Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SVCS-421] Remove unsizable response reader flag #309

52 changes: 52 additions & 0 deletions tests/core/streams/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from http import HTTPStatus

import pytest

from tests.utils import MockCoroutine
from waterbutler.core.streams.http import ResponseStreamReader


@pytest.fixture
def mock_content():
return type('mock_content', (object,), {'read': MockCoroutine(return_value=b'data')})


@pytest.fixture
def mock_content_eof():
return type('mock_content_eof', (object,), {'read': MockCoroutine(return_value=None)})


class MockResponse:
status = HTTPStatus.OK
headers = {'Content-Length': 100, 'Content-Range': '0-100'}
content = mock_content()
release = MockCoroutine()


class MockResponseNoContentLength:
status = HTTPStatus.OK
headers = {'Content-Range': '0-0'}
content = mock_content()
release = MockCoroutine()


class MockResponseNoContent:
status = HTTPStatus.OK
headers = {'Content-Range': '0-0'}
content = mock_content_eof()
release = MockCoroutine()


@pytest.fixture
def mock_response_stream_reader():
return ResponseStreamReader(MockResponse(), size=None, name='test stream')


@pytest.fixture
def mock_response_stream_reader_no_size():
return ResponseStreamReader(MockResponseNoContentLength(), size=None, name='test stream')


@pytest.fixture
def mock_response_stream_reader_no_content():
return ResponseStreamReader(MockResponseNoContent(), size=None, name='test stream')
38 changes: 38 additions & 0 deletions tests/core/streams/test_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from unittest import mock

import pytest

from tests.core.streams.fixtures import (mock_content_eof, MockResponseNoContent,
mock_content, MockResponseNoContentLength,
mock_response_stream_reader, MockResponse,
mock_response_stream_reader_no_size,
mock_response_stream_reader_no_content)


class TestResponseStreamReader:

@pytest.mark.asyncio
async def test_response_stream_reader(self, mock_response_stream_reader):
assert mock_response_stream_reader.name == 'test stream'
assert mock_response_stream_reader.size == 100
assert not mock_response_stream_reader.partial
assert mock_response_stream_reader.content_type == 'application/octet-stream'
assert mock_response_stream_reader.content_range == '0-100'
assert (await mock_response_stream_reader.read()) == b'data'

@pytest.mark.asyncio
async def test_response_stream_reader_no_size(self, mock_response_stream_reader_no_size):
assert mock_response_stream_reader_no_size.name == 'test stream'
assert mock_response_stream_reader_no_size.size is None
assert not mock_response_stream_reader_no_size.partial
assert mock_response_stream_reader_no_size.content_type == 'application/octet-stream'
assert mock_response_stream_reader_no_size.content_range == '0-0'
assert (await mock_response_stream_reader_no_size.read()) == b'data'

@pytest.mark.asyncio
async def test_response_stream_reader_eof(self, mock_response_stream_reader_no_content):

mock_response_stream_reader_no_content.feed_eof = mock.Mock()
assert (await mock_response_stream_reader_no_content.read()) is None
mock_response_stream_reader_no_content.feed_eof.assert_called_once_with()
MockResponseNoContent.release.assert_called_once_with()
36 changes: 17 additions & 19 deletions waterbutler/core/streams/http.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import asyncio
import uuid
import asyncio

from waterbutler.core.streams.base import BaseStream
from waterbutler.core.streams.base import MultiStream
from waterbutler.core.streams.base import StringStream
from waterbutler.core.streams.base import BaseStream, MultiStream, StringStream


class FormDataStream(MultiStream):
"""A child of MultiSteam used to create stream friendly multipart form data requests.
Usage:

>>> stream = FormDataStream(key1='value1', file=FileStream(...))
>>> stream = FormDataStream(key1='value1', file=FileStream(...))

Or:

Expand All @@ -29,9 +27,7 @@ class FormDataStream(MultiStream):

@classmethod
def make_boundary(cls):
"""Creates a random-ish boundary for
form data seperator
"""
"""Creates a random-ish boundary for form data separator"""
return uuid.uuid4().hex

@classmethod
Expand All @@ -41,15 +37,12 @@ def make_header(cls, name, disposition='form-data', additional_headers=None, **e

header += ''.join([
'; {}="{}"'.format(key, value)
for key, value
in extra.items()
if value is not None
for key, value in extra.items() if value is not None
])

additional = '\r\n'.join([
'{}: {}'.format(key, value)
for key, value in additional_headers.items()
if value is not None
for key, value in additional_headers.items() if value is not None
])

header += '\r\n'
Expand Down Expand Up @@ -114,7 +107,15 @@ def add_field(self, key, value):
StringStream(self.make_header(key) + value + '\r\n')
)

def add_file(self, field_name, file_stream, file_name=None, mime='application/octet-stream', disposition='file', transcoding='binary'):
def add_file(
self,
field_name,
file_stream,
file_name=None,
mime='application/octet-stream',
disposition='file',
transcoding='binary'
):
assert self.can_add_more, 'Cannot add more fields after calling finalize or read'

header = self.make_header(
Expand All @@ -140,15 +141,12 @@ def _make_boundary_stream(self):

class ResponseStreamReader(BaseStream):

def __init__(self, response, size=None, name=None, unsizable=False):
def __init__(self, response, size=None, name=None):
super().__init__()
if 'Content-Length' in response.headers:
self._size = int(response.headers['Content-Length'])
elif not unsizable:
self._size = int(size)
else:
self._size = None

self._size = size
self._name = name
self.response = response

Expand Down