RedisJSON / redisjson-py

An extension to redis-py for using Redis' ReJSON module
https://redisjson.io
BSD 2-Clause "Simplified" License
160 stars 34 forks source link

Client.jsonget not finding key from rootpath #24

Open gr33ngiant112 opened 5 years ago

gr33ngiant112 commented 5 years ago

I'm entirely certain this may be a usage issue, but reaching out here as I'm not sure where else to look.

Problem

I have a class Jobs which inherits the rejson module classes Client and Path. Methods in the Jobs class are called by routes in a flask API.

My /run route calls the make_job function, which in turn generates a unique id for that job, and inserts a JSON body into redis. I am using the redis-json docker image from dockerhub.

My /status route calls the get_job function, and is supposed to return a JSON object from redis.

The problem I'm having is, I can insert JSON objects without issue, but jsonget can't seem to find the key I inserted previously.

All keys are inserted at the root path.

Workflow

Insert Key via /run route:

{
  "JobID": "5de40954-bf5f-499e-8d6e-f283d4d326c8",
}

Verify via redis-cli:

127.0.0.1:6379> KEYS 5de40954-bf5f-499e-8d6e-f283d4d326c8
1) "5de40954-bf5f-499e-8d6e-f283d4d326c8"
127.0.0.1:6379> 
127.0.0.1:6379> json.get 5de40954-bf5f-499e-8d6e-f283d4d326c8
"{\"gitOrg\":\"test-org\",\"gitRepo\":\"test-repo\",\"job\":{\"inventory\":\"site.ini\",\"playbook\":\"deploy.yml\",\"vars\":\"env=testenv cleanup_keys=true\"}}"

Run /status route that calls get_job(), which gets a key using jsonget;

None
127.0.0.1 - - [12/Jul/2019 16:24:02] "GET /status/5de40954-bf5f-499e-8d6e-f283d4d326c8 HTTP/1.1" 404 -

where None is the output of print(job)

Code:

from rejson import Client, Path

class Jobs(Client, Path):
    Client(host='localhost', port=6379, decode_responses=True)

    def make_job_id(self):
        return uuid.uuid4()

    def make_job(self, data): 
        jobId = str(self.make_job_id())
        self.jsonset(jobId, Path.rootPath(), data)
        return jobId

    def get_job(self, jobId):
        job = self.jsonget(jobId)
        if job:
            print(job)
            return job
        else:
            return False

Expected results

jsonget returns and decodes the JSON object stored under the keyname set when inserted.

I'm not sure if this is a pathing issue. I've looked at the source, and I see that jsonget defaults to the root path.

Before using rejson-py, I was using just the raw redis client and encoding/decoding json as needed, but I had issues where the first key I inserted into redis would be rendered null, so I switched to rejson-py.

Idk if I'm being incredibly stupid here or what..

gr33ngiant112 commented 5 years ago

This might be a clue, I just don't know how to interpret it.

I modified the get_job function to run a raw redis command.

 def get_job(self, jobId):
        reply = self.execute_command('JSON.GET', jobId)
        print(reply)
        return reply

When called, throws the error:

    return self.response_callbacks[command_name](response, **options)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
TypeError: cannot use a string pattern on a bytes-like object
bentsku commented 5 years ago

Hello! Your code seems to be good, but the None answer from the client, and the error thrown with your second test confirm that there are no JSON data under that key. Have you tried the same way as you did with the redis-cli, but with Python ? With the key hard-written in the code ? I'd say that the jobId parameter in the get_jobmethod maybe isn't what you think it is ? It is the only answer I can think of at the moment.

rc0r commented 5 years ago

This seems to be a decoding problem of the data stored as bytes() in Redis.

Replacing the default JSONDecoder with the following did the trick for me:

from rejson import Client, Path
from json import JSONDecoder

class RedisJsonDecoder(JSONDecoder):
    def decode(self, s, *args, **kwargs):
        if isinstance(s, bytes):
            s = s.decode('UTF-8')
        return super(RedisJsonDecoder, self).decode(s, *args, **kwargs)

r = Client(decoder=RedisJsonDecoder())
# ...

HTH!