Hazelcast Python Client 3.9: Introducing the Python 3 Support

We are happy to announce the release of Hazelcast Python Client 3.9! This new release brings the long-awaited Python 3 support to our client with bug fixes and performance improvements.

Introduction

As the number of packages that support Python 3 grows day by day, supporting Python 3 without dropping support for Python 2.7 was a necessary step for Hazelcast to take. This is what we had in our minds while preparing the 3.9 release.

Hazelcast Python Client now supports Python 3.4, 3.5, 3.6 and 3.7 along with Python 2.7. Let’s install this new release to see it in action!

Installation

We updated our package on the PyPI so you can install Hazelcast Python Client 3.9 easily with pip:

pip install hazelcast-python-client

Another option for installing the Hazelcast Python Client is downloading it from here and running the following command after extracting the zipped file:

python setup.py install

Confirm your installation by executing the following command on your console. It should produce “3.9” as output.

python -c "import hazelcast;print(hazelcast.__version__)"

When everything works as expected, you are ready to go to the next section to learn more about our Python client.

Quick Overview

Hazelcast Python Client supports tons of different features and data structures. Let’s take a look at the most famous of them, Map (equivalent of a distributed dictionary).

We first start with configuring a logger for the client. This is not required for Hazelcast Python Client to work but we highly recommend doing it so to see what is going on.

import hazelcast
import logging

logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)

Now you need a configuration for the client. You can simply configure your client to connect an already running Hazelcast cluster on “10.212.1.111:5701” as below.

config = hazelcast.ClientConfig()
config.network_config.addresses.append("10.212.1.111:5701")

There are lots of different options to customize your client. Hazelcast will assign default values for those options unless you explicitly state them. For example, if you don’t configure your client to connect the cluster running on “10.212.1.111:5701” as described above, Hazelcast will assume that you want to connect “127.0.0.1”, i.e., your localhost.

You can easily initialize a Hazelcast client with your customized configuration by giving it to a HazelcastClient object as a parameter. Unless you want to connect to multiple clusters, you should create a single instance of the HazelcastClient class.

client = hazelcast.HazelcastClient(config)

We are now connected to our cluster and ready to do read and write operations on it.

As we always say, Hazelcast is easy to use. You can access a distributed object on your cluster with a simple method call. You just need to provide the name of your distributed object, and Hazelcast will get the object with the given name if it already exists on the cluster or create a new object with that name for you.

async_map = client.get_map("async-map")

Hazelcast Python Client is designed to be fully asynchronous. Therefore, every method call over distributed objects such as put, get, add, etc. will return a Future object. Results of the Future objects can be retrieved by calling result() function.

In the following code snippet, we will fill our map with 10 key-value pairs. Each call of the put method will return the old value associated with that key. If that key wasn’t present in the map before, the put method will return None.

for value in range(10):
    key = "key-" + str(value)
    old_value = async_map.put(key, value).result()
    print("Old value for", key, "is", old_value)

You can get the value associated to a key with the get method.

print("Get operation: ", async_map.get("key-0").result())

You can iterate over the map with the entry_set method. It will return a copy of the key-value pairs in a list of tuples format.

print("Iterating over async map")
for key, value in async_map.entry_set().result():
    print(key, value)

Result method is not the only way to retrieve the return values of distributed object methods. You can also attach a callback function to your Future object, and it will be called, with the future as its only argument, when the future finishes running.

print("Iterating over async map with callback")
def async_callback(f):
    for key, value in f.result():
        print(key, value)

async_map.entry_set().add_done_callback(async_callback)

Async operations are more efficient in single threaded Python interpreter but we sometimes want to write a simple code without result methods or callbacks. Hazelcast Python Client provides a helper function for all of its distributed objects for that case.

When you access a distributed object by calling blocking method over it, all of the following method calls over that object will be blocking. That means, blocking calls will block the execution until the return value is calculated and return that value directly. You basically will get a distributed object that works synchronously.

Let’s repeat the example above with a blocking map to see the difference.

blocking_map = client.get_map("blocking-map").blocking()
for value in range(10):
    key = "key-" + str(value)
    old_value = blocking_map.put(key, value)
    print("Old value for", key, "is", old_value)

print("Get operation: ", blocking_map.get("key-0"))

print("Iterating over blocking map")
for key, value in blocking_map.entry_set():
    print(key, value)

All initialized HazelcastClient instances should be shut down when they are not needed anymore to free up resources. Let’s do that and finish the quick overview.

client.shutdown()

I hope you got the essence of our Python client. If you are curious to see more of its capabilities take a look at our documentation and example codes.

In the next section, I will inform you about an important API change that comes with 3.9 release.

API Change

We planned to preserve the API of Hazelcast Python Client while porting it to Python 3. We managed to keep up with this plan except for one thing: name of a method.

In Python 3.5, await and async syntax added to the language and in Python 3.7 they become reserved keywords. Our CountDownLatch class used to have a method named await that would give SyntaxError in Python 3.7 because you cannot name a variable or a method with a keyword.

We want Hazelcast Python Client to support Python 3.7 and any version that would come after it. Therefore, we had to change this method’s name. We thought await_latch would be a reasonable substitute for await and renamed our method as such.

While making this change, we wanted to make sure that our existing users are not affected by it and can safely update their client to 3.9 in Python 2.7. To accomplish this, we have also defined an await method that calls the await_latch method on the background only on Python 2. This means that Python 2 users will be able to update their client to 3.9 without changing a single line of their code. However, if you want to port your application to Python 3 or develop a new application that uses Hazelcast Python Client 3.9 in Python 3, you should use await_latch instead.

TL;DR: await method of CountDownLatch is renamed as await_latch but you can still use await in Python 2.

Closing Words

Hazelcast Python Client 3.9 was a great step in bringing it back to life. We encourage everyone to grab and play with it on Python 2 and 3. We are eager to hear what you think so please stop by at our Gitter channel channel or Google groups. If you would like to introduce some changes or contribute, please visit our Github repository.