Search code examples
pythonredisredis-py

Is it possible to store nested dict in Redis as hash structure?


I am trying to store a nested dictionary as hash with HSET, but that does not seem to be possible.

An example that does not work:

from redis import StrictRedis
foo = {
    "host_data": {
        "hostname": "some_host",
        "mac_address": "82:fa:8e:63:40:05",
        "root_password": {
            "is_crypted": True,
            "password": "sha512_password"
        },
        "network": {
            "ip_address": "192.168.0.10/24",
            "default_gateway": "192.168.0.1",
            "vmnic": "vmnic3",
            "vlan": 20
        },
        "dns_servers": [
            "dns01.local",
            "dns02.local"
        ]
    },
    "installation_type": "esxi",
    "image_data": {
        "type": "vCenter_contentlib",
        "host": "vcenter01",
        "credential": {
            "username": "some_user",
            "password": "some_password"
        },
        "content_library": "the_content_lib_name",
        "image_name": "some_image"
    },
    "host_short_name": "esxi021"
}

redis_connection = StrictRedis(host='localhost', port=6379, charset="utf-8", decode_responses=True)
redis_connection.hset("test", mapping=foo)

Throws the following error:

Traceback (most recent call last):
  File "/Users/project/dummy.py", line 36, in <module>
    redis_connection.hset("test", mapping=thing)
  File "/Users/project/venv/lib/python3.9/site-packages/redis/client.py", line 3050, in hset
    return self.execute_command('HSET', name, *items)
  File "/Users/project/venv/lib/python3.9/site-packages/redis/client.py", line 900, in execute_command
    conn.send_command(*args)
  File "/Users/project/venv/lib/python3.9/site-packages/redis/connection.py", line 725, in send_command
    self.send_packed_command(self.pack_command(*args),
  File "/Users/project/venv/lib/python3.9/site-packages/redis/connection.py", line 775, in pack_command
    for arg in imap(self.encoder.encode, args):
  File "/Users/project/venv/lib/python3.9/site-packages/redis/connection.py", line 119, in encode
    raise DataError("Invalid input of type: '%s'. Convert to a "
redis.exceptions.DataError: Invalid input of type: 'dict'. Convert to a bytes, string, int or float first.

An example that works:

from redis import StrictRedis
foo = {
    "installation_type": "esxi",
    "host_short_name": "esxi021"
}

redis_connection = StrictRedis(host='localhost', port=6379, charset="utf-8", decode_responses=True)
redis_connection.hset("test", mapping=foo)

In summary, my question is:

Is it possible to store nested dict as hash structure in Redis?

NOTE: I am aware it can be stored as string and then loaded as json, but I am really trying to avoid it.

EDIT: Reids version is 6.2.5


Solution

  • Redis alone doesn't store nested structures in hashes. There is a good answer for how to work around here.

    However, there is a module RedisJSON that supports this and the python redis client includes support for this module.

    Load the module in the redis server:

    redis-server --loadmodule ../RedisJSON/target/release/librejson.dylib
    

    In python you can then set and retrieve nested dicts or portions of the path.

    import redis
    from redis.commands.json.path import Path
    
    redis_connection = StrictRedis(host='localhost', port=6379, charset="utf-8", decode_responses=True)
    
    foo = {
        "host_data": {
            "hostname": "some_host",
            "mac_address": "82:fa:8e:63:40:05",
            "root_password": {
                "is_crypted": True,
                "password": "sha512_password"
            },
            "network": {
                "ip_address": "192.168.0.10/24",
                "default_gateway": "192.168.0.1",
                "vmnic": "vmnic3",
                "vlan": 20
            },
            "dns_servers": [
                "dns01.local",
                "dns02.local"
            ]
        },
        "installation_type": "esxi",
        "image_data": {
            "type": "vCenter_contentlib",
            "host": "vcenter01",
            "credential": {
                "username": "some_user",
                "password": "some_password"
            },
            "content_library": "the_content_lib_name",
            "image_name": "some_image"
        },
        "host_short_name": "esxi021"
    }
    
    redis_connection.json().set("test", Path.rootPath(), foo)
    print(redis_connection.json().get("test", '.host_data.hostname'))