Merge pull request #3789 from shin-/3788-json-splitter-fix

json_splitter: Don't break when buffer contains leading whitespace.
This commit is contained in:
Aanand Prasad 2016-08-03 13:23:52 +01:00 committed by GitHub
commit d29f8e1022
4 changed files with 37 additions and 2 deletions

View File

@ -23,6 +23,7 @@ from ..config.environment import Environment
from ..config.serialize import serialize_config from ..config.serialize import serialize_config
from ..const import DEFAULT_TIMEOUT from ..const import DEFAULT_TIMEOUT
from ..const import IS_WINDOWS_PLATFORM from ..const import IS_WINDOWS_PLATFORM
from ..errors import StreamParseError
from ..progress_stream import StreamOutputError from ..progress_stream import StreamOutputError
from ..project import NoSuchService from ..project import NoSuchService
from ..project import OneOffFilter from ..project import OneOffFilter
@ -75,7 +76,7 @@ def main():
except NeedsBuildError as e: except NeedsBuildError as e:
log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name) log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name)
sys.exit(1) sys.exit(1)
except errors.ConnectionError: except (errors.ConnectionError, StreamParseError):
sys.exit(1) sys.exit(1)

View File

@ -5,3 +5,8 @@ from __future__ import unicode_literals
class OperationFailedError(Exception): class OperationFailedError(Exception):
def __init__(self, reason): def __init__(self, reason):
self.msg = reason self.msg = reason
class StreamParseError(RuntimeError):
def __init__(self, reason):
self.msg = reason

View File

@ -5,11 +5,15 @@ import codecs
import hashlib import hashlib
import json import json
import json.decoder import json.decoder
import logging
import six import six
from .errors import StreamParseError
json_decoder = json.JSONDecoder() json_decoder = json.JSONDecoder()
log = logging.getLogger(__name__)
def get_output_stream(stream): def get_output_stream(stream):
@ -60,13 +64,21 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a):
yield item yield item
if buffered: if buffered:
try:
yield decoder(buffered) yield decoder(buffered)
except Exception as e:
log.error(
'Compose tried decoding the following data chunk, but failed:'
'\n%s' % repr(buffered)
)
raise StreamParseError(e)
def json_splitter(buffer): def json_splitter(buffer):
"""Attempt to parse a json object from a buffer. If there is at least one """Attempt to parse a json object from a buffer. If there is at least one
object, return it and the rest of the buffer, otherwise return None. object, return it and the rest of the buffer, otherwise return None.
""" """
buffer = buffer.strip()
try: try:
obj, index = json_decoder.raw_decode(buffer) obj, index = json_decoder.raw_decode(buffer)
rest = buffer[json.decoder.WHITESPACE.match(buffer, index).end():] rest = buffer[json.decoder.WHITESPACE.match(buffer, index).end():]

View File

@ -15,6 +15,10 @@ class TestJsonSplitter(object):
data = '{"foo": "bar"}\n \n{"next": "obj"}' data = '{"foo": "bar"}\n \n{"next": "obj"}'
assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}') assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}')
def test_json_splitter_leading_whitespace(self):
data = '\n \r{"foo": "bar"}\n\n {"next": "obj"}'
assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}')
class TestStreamAsText(object): class TestStreamAsText(object):
@ -43,3 +47,16 @@ class TestJsonStream(object):
[1, 2, 3], [1, 2, 3],
[], [],
] ]
def test_with_leading_whitespace(self):
stream = [
'\n \r\n {"one": "two"}{"x": 1}',
' {"three": "four"}\t\t{"x": 2}'
]
output = list(utils.json_stream(stream))
assert output == [
{'one': 'two'},
{'x': 1},
{'three': 'four'},
{'x': 2}
]