watch: Avoid double-decoding in python3

Both iter_content and the first line of the loop in the
etcd3gw watch code were decoding the chunk received.

In python2, this works fine:
```
$ python2 -c "print(b'hello_world'.decode().decode('utf-8'))"
hello_world
```

In python3, it raises an AttributeError:
```
$ python3 -c "print(b'hello_world'.decode().decode('utf-8'))"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'str' object has no attribute 'decode'
```

Thus, etcd3gw's watch is broken in python3.  This commit
fixes it by only decoding the line once.

Signed-off-by: Tyler J. Stachecki <tstachecki@bloomberg.net>
Change-Id: I203574a1ef4996a43860350be59fbe208457562d
Closes-Bug: 1950517
This commit is contained in:
Tyler J. Stachecki 2021-11-10 17:32:03 -05:00 committed by Tyler Stachecki
parent 9d50411b6f
commit ed899b34e4
2 changed files with 32 additions and 1 deletions

@ -17,12 +17,16 @@ test_etcd3-gateway
Tests for `etcd3gw` module.
"""
import base64
import json
import requests
import six
import threading
import time
import uuid
from testtools.testcase import unittest
from unittest import mock
import urllib3
from etcd3gw.client import Etcd3Client
@ -404,3 +408,30 @@ class TestEtcd3Gateway(base.TestCase):
keys = lease.keys()
self.assertEqual(1, len(keys))
self.assertIn(six.b(key), keys)
def my_iter_content(self, *args, **kwargs):
payload = json.dumps({
'result': {
'events': [{
'kv': {'key': base64.b64encode(b'value').decode('utf-8')},
}]
}
})
if not kwargs.get('decode_unicode', False):
payload = payload.encode()
return [payload]
@mock.patch.object(requests.Response, 'iter_content', new=my_iter_content)
@mock.patch.object(requests.sessions.Session, 'post')
def test_watch_unicode(self, mock_post):
mocked_response = requests.Response()
mocked_response.connection = mock.Mock()
mock_post.return_value = mocked_response
try:
res = self.client.watch_once('/some/key', timeout=1)
except exceptions.WatchTimedOut:
self.fail("watch timed out when server responded with unicode")
self.assertEqual(res, {'kv': {'key': b'value'}})

@ -19,7 +19,7 @@ from etcd3gw.utils import _get_threadpool_executor
def _watch(resp, callback):
for line in resp.iter_content(chunk_size=None, decode_unicode=True):
for line in resp.iter_content(chunk_size=None, decode_unicode=False):
decoded_line = line.decode('utf-8')
payload = json.loads(decoded_line)
if 'created' in payload['result']: