Commit a3892f79 authored by Jérome Perrin's avatar Jérome Perrin

big_file: py3

parent 70371e6b
......@@ -14,7 +14,6 @@
#
##############################################################################
from six.moves import cStringIO as StringIO
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Base import removeIContentishInterface
......@@ -23,14 +22,17 @@ from erp5.component.module.BTreeData import BTreeData
from ZPublisher.HTTPRequest import FileUpload
from ZPublisher import HTTPRangeSupport
from zope.datetime import rfc1123_date
from mimetools import choose_boundary
from Products.CMFCore.utils import _setCacheHeaders, _ViewEmulator
from DateTime import DateTime
import re
import io
import six
if six.PY3:
long = int # pylint:disable=redefined-builtin
from email.generator import _make_boundary as choose_boundary
else:
from mimetools import choose_boundary # pylint:disable=import-error
class BigFile(File):
"""
......@@ -43,10 +45,10 @@ class BigFile(File):
data property is either
- BTreeData instance, or
- str(*), or
- bytes(*), or
- None.
(*) str has to be supported because '' is a default value for `data` field
(*) bytes has to be supported because b'' is a default value for `data` field
from Data property sheet.
Even more - for
......@@ -55,7 +57,7 @@ class BigFile(File):
b) desire to support automatic migration of File-based documents
from document_module to BigFiles
non-empty str for data also have to be supported.
non-empty bytes for data also have to be supported.
XXX(kirr) I'm not sure supporting non-empty str is a good idea (it
would be simpler if .data could be either BTreeData or "empty"),
......@@ -64,6 +66,8 @@ class BigFile(File):
We discussed with Romain and settled on "None or str or BTreeData"
invariant for now.
notes: for python3 port "str" becomes "bytes", but kirr message was not modified.
"""
meta_type = 'ERP5 Big File'
......@@ -115,9 +119,9 @@ class BigFile(File):
# of memory.
n=1 << 16
if isinstance(file, str):
if isinstance(file, bytes):
# Big string: cut it into smaller chunks
file = StringIO(file)
file = io.BytesIO(file)
if isinstance(file, FileUpload) and not file:
raise ValueError('File not specified')
......@@ -130,9 +134,9 @@ class BigFile(File):
if data is None:
btree = BTreeData()
elif isinstance(data, str):
elif isinstance(data, bytes):
# we'll want to append content to this file -
# - automatically convert str (empty or not) to BTreeData
# - automatically convert bytes (empty or not) to BTreeData
btree = BTreeData()
btree.write(data, 0)
else:
......@@ -236,7 +240,7 @@ class BigFile(File):
RESPONSE.setStatus(206) # Partial content
# NOTE data cannot be None here (if it is - ranges are not satisfiable)
if isinstance(data, str):
if isinstance(data, bytes):
RESPONSE.write(data[start:end])
return True
for chunk in data.iterate(start, end-start):
......@@ -271,22 +275,22 @@ class BigFile(File):
RESPONSE.setStatus(206) # Partial content
for start, end in ranges:
RESPONSE.write('\r\n--%s\r\n' % boundary)
RESPONSE.write('Content-Type: %s\r\n' %
self.content_type)
RESPONSE.write(('\r\n--%s\r\n' % boundary).encode())
RESPONSE.write(('Content-Type: %s\r\n' %
self.content_type).encode())
RESPONSE.write(
'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
start, end - 1, self.getSize()))
('Content-Range: bytes %d-%d/%d\r\n\r\n' % (
start, end - 1, self.getSize())).encode())
# NOTE data cannot be None here (if it is - ranges are not satisfiable)
if isinstance(data, str):
if isinstance(data, bytes):
RESPONSE.write(data[start:end])
else:
for chunk in data.iterate(start, end-start):
RESPONSE.write(chunk)
RESPONSE.write('\r\n--%s--\r\n' % boundary)
RESPONSE.write(('\r\n--%s--\r\n' % boundary).encode())
return True
security.declareProtected(Permissions.View, 'index_html')
......@@ -296,7 +300,7 @@ class BigFile(File):
"""
if self._range_request_handler(REQUEST, RESPONSE):
# we served a chunk of content in response to a range request.
return ''
return b''
web_cache_kw = kw.copy()
if format is not _MARKER:
......@@ -327,13 +331,13 @@ class BigFile(File):
if data is None:
return ''
if isinstance(data, str):
return b''
if isinstance(data, bytes):
RESPONSE.setBase(None)
return data
for chunk in data.iterate():
RESPONSE.write(chunk)
return ''
return b''
security.declareProtected(Permissions.ModifyPortalContent,'PUT')
def PUT(self, REQUEST, RESPONSE):
......
......@@ -3,6 +3,7 @@ from BTrees.LOBTree import LOBTree
from persistent import Persistent
import itertools
from six.moves import range
import six
# Maximum memory to allocate for sparse-induced padding.
MAX_PADDING_CHUNK = 2 ** 20
......@@ -11,11 +12,13 @@ class PersistentString(Persistent):
def __init__(self, value):
self.value = value
def __str__(self):
def __bytes__(self):
return self.value
if six.PY2:
__str__ = __bytes__
# Save place when storing this data in zodb
__getstate__ = __str__
__getstate__ = __bytes__
__setstate__ = __init__
negative_offset_error = ValueError('Negative offset')
......@@ -110,7 +113,7 @@ class BTreeData(Persistent):
chunk = tree[lower_key]
chunk_end = lower_key + len(chunk.value)
if chunk_end > offset or (
len(chunk.value) < self._chunk_size and
len(chunk.value) < (self._chunk_size or 0) and
chunk_end == offset
):
key = lower_key
......@@ -137,7 +140,7 @@ class BTreeData(Persistent):
try:
chunk = tree[key]
except KeyError:
tree[key] = chunk = PersistentString('')
tree[key] = chunk = PersistentString(b'')
entry_size = len(chunk.value)
if entry_size < to_write_len:
to_write_len = min(to_write_len, max_to_write_len)
......@@ -158,9 +161,9 @@ class BTreeData(Persistent):
size (int)
Number of bytes to read.
Returns string of read data.
Returns bytes of read data.
"""
return ''.join(self.iterate(offset, size))
return b''.join(self.iterate(offset, size))
def iterate(self, offset=0, size=None):
"""
......@@ -243,7 +246,7 @@ class BTreeData(Persistent):
except ValueError:
break
del tree[key]
self.write('', offset)
self.write(b'', offset)
# XXX: Various batch_size values need to be benchmarked, and a saner
# default is likely to be applied.
......@@ -314,12 +317,11 @@ class BTreeData(Persistent):
tree[key] = next_chunk
if __name__ == '__main__':
def check(tree, length, read_offset, read_length, data_, keys=None):
print(list(tree._tree.items()))
tree_length = len(tree)
tree_data = tree.read(read_offset, read_length)
tree_iterator_data = ''.join(tree.iterate(read_offset, read_length))
tree_iterator_data = b''.join(tree.iterate(read_offset, read_length))
assert tree_length == length, tree_length
assert tree_data == data_, repr(tree_data)
assert tree_iterator_data == data_, repr(tree_iterator_data)
......
......@@ -24,8 +24,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from six.moves import cStringIO as StringIO
import io
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
......@@ -35,15 +34,15 @@ from erp5.component.module.BTreeData import BTreeData
# like Testing.makerequest, but
#
# 1. always redirects stdout to stringio,
# 1. always redirects stdout to BytesIO,
# 2. stdin content can be specified and is processed,
# 3. returns actual request object (not wrapped portal).
#
# see also: Products.CMFCore.tests.test_CookieCrumbler.makerequest()
# TODO makerequest() is generic enough and deserves moving to testing utils
def makerequest(environ=None, stdin=''):
stdout = StringIO()
stdin = StringIO(stdin)
def makerequest(environ=None, stdin=b''):
stdout = io.BytesIO()
stdin = io.BytesIO(stdin)
if environ is None:
environ = {}
......@@ -65,7 +64,7 @@ def makerequest(environ=None, stdin=''):
# generate makerequest-like function for http method
def request_function(method_name):
method_name = method_name.upper()
def method_func(environ=None, stdin=''):
def method_func(environ=None, stdin=b''):
if environ is None:
environ = {}
environ.setdefault('REQUEST_METHOD', method_name)
......@@ -112,7 +111,7 @@ class TestBigFile(ERP5TypeTestCase):
ret = method (request, request.RESPONSE, **kw)
# like in ZPublisher - returned RESPONSE means empty
if ret is request.RESPONSE:
ret = ''
ret = b''
self.assertEqual(ret, result)
self.assertEqual(status, request.RESPONSE.getStatus())
for h,v in header_dict.items():
......@@ -120,9 +119,9 @@ class TestBigFile(ERP5TypeTestCase):
self.assertEqual(v, rv, '%s: %r != %r' % (h, v, rv))
# force response flush to its stdout
request.RESPONSE.write('')
request.RESPONSE.write(b'')
# body and headers are delimited by empty line (RFC 2616, 4.1)
response_body = request.RESPONSE.stdout.getvalue().split('\r\n\r\n', 1)[1]
response_body = request.RESPONSE.stdout.getvalue().split(b'\r\n\r\n', 1)[1]
self.assertEqual(body, response_body)
......@@ -135,90 +134,90 @@ class TestBigFile(ERP5TypeTestCase):
# after creation file is empty and get(0-0) returns 416
self.assertEqual(f.getSize(), 0)
self.assertEqual(f.getData(), '')
self.assertEqual(f.getData(), b'')
# result body status headers
check(get(), {'format': 'raw'}, '', '', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, '', '', 416, {'Content-Length': '0', 'Content-Range': 'bytes */0'})
check(get(), {'format': 'raw'}, b'', b'', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, b'', b'', 416, {'Content-Length': '0', 'Content-Range': 'bytes */0'})
# append empty chunk - the same
f._appendData('')
f._appendData(b'')
self.assertEqual(f.getSize(), 0)
self.assertEqual(f.getData(), '')
self.assertEqual(f.getData(), b'')
check(get(), {'format': 'raw'}, '', '', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, '', '', 416, { 'Content-Range': 'bytes */0'})
check(get(), {'format': 'raw'}, b'', b'', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, b'', b'', 416, { 'Content-Range': 'bytes */0'})
# append 1 byte - file should grow up and get(0-0) returns 206
f._appendData('x')
f._appendData(b'x')
self.assertEqual(f.getSize(), 1)
self.assertEqual(f.getData(), 'x')
self.assertEqual(f.getData(), b'x')
check(get(), {'format': 'raw'}, '', 'x', 200, {'Content-Length': '1'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-0'})
check(get({ 'Range': 'bytes=0-0'}),{}, '', 'x', 206, {'Content-Length': '1', 'Content-Range': 'bytes 0-0/1'})
check(get(), {'format': 'raw'}, b'', b'x', 200, {'Content-Length': '1'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-0'})
check(get({ 'Range': 'bytes=0-0'}),{}, b'', b'x', 206, {'Content-Length': '1', 'Content-Range': 'bytes 0-0/1'})
# append another 2 bytes and try to get whole file and partial contents
f._appendData('yz')
f._appendData(b'yz')
self.assertEqual(f.getSize(), 3)
self.assertEqual(f.getData(), 'xyz')
self.assertEqual(f.getData(), b'xyz')
check(get(), {'format': 'raw'}, '', 'xyz', 200, {'Content-Length': '3'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-2'})
check(get({ 'Range': 'bytes=0-0'}),{}, '', 'x' , 206, {'Content-Length': '1', 'Content-Range': 'bytes 0-0/3'})
check(get({ 'Range': 'bytes=1-1'}),{}, '', 'y' , 206, {'Content-Length': '1', 'Content-Range': 'bytes 1-1/3'})
check(get({ 'Range': 'bytes=2-2'}),{}, '', 'z', 206, {'Content-Length': '1', 'Content-Range': 'bytes 2-2/3'})
check(get({ 'Range': 'bytes=0-1'}),{}, '', 'xy' , 206, {'Content-Length': '2', 'Content-Range': 'bytes 0-1/3'})
check(get({ 'Range': 'bytes=1-2'}),{}, '', 'yz', 206, {'Content-Length': '2', 'Content-Range': 'bytes 1-2/3'})
check(get({ 'Range': 'bytes=0-2'}),{}, '', 'xyz', 206, {'Content-Length': '3', 'Content-Range': 'bytes 0-2/3'})
check(get(), {'format': 'raw'}, b'', b'xyz', 200, {'Content-Length': '3'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-2'})
check(get({ 'Range': 'bytes=0-0'}),{}, b'', b'x' , 206, {'Content-Length': '1', 'Content-Range': 'bytes 0-0/3'})
check(get({ 'Range': 'bytes=1-1'}),{}, b'', b'y' , 206, {'Content-Length': '1', 'Content-Range': 'bytes 1-1/3'})
check(get({ 'Range': 'bytes=2-2'}),{}, b'', b'z', 206, {'Content-Length': '1', 'Content-Range': 'bytes 2-2/3'})
check(get({ 'Range': 'bytes=0-1'}),{}, b'', b'xy' , 206, {'Content-Length': '2', 'Content-Range': 'bytes 0-1/3'})
check(get({ 'Range': 'bytes=1-2'}),{}, b'', b'yz', 206, {'Content-Length': '2', 'Content-Range': 'bytes 1-2/3'})
check(get({ 'Range': 'bytes=0-2'}),{}, b'', b'xyz', 206, {'Content-Length': '3', 'Content-Range': 'bytes 0-2/3'})
# append via PUT with range
check(put({'Content-Range': 'bytes 3-4/5', 'Content-Length': '2'}, '01'),{}, '', '', 204, {})
check(put({'Content-Range': 'bytes 3-4/5', 'Content-Length': '2'}, b'01'),{}, b'', b'', 204, {})
self.assertEqual(f.getSize(), 5)
self.assertEqual(f.getData(), 'xyz01')
check(get(), {'format': 'raw'}, '', 'xyz01',200, {'Content-Length': '5'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-4'})
check(get({ 'Range': 'bytes=0-4'}),{}, '', 'xyz01',206, {'Content-Length': '5', 'Content-Range': 'bytes 0-4/5'})
check(get({ 'Range': 'bytes=1-3'}),{}, '', 'yz0' ,206, {'Content-Length': '3', 'Content-Range': 'bytes 1-3/5'})
check(get({ 'Range': 'bytes=1-2'}),{}, '', 'yz' ,206, {'Content-Length': '2', 'Content-Range': 'bytes 1-2/5'})
check(get({ 'Range': 'bytes=2-2'}),{}, '', 'z' ,206, {'Content-Length': '1', 'Content-Range': 'bytes 2-2/5'})
self.assertEqual(f.getData(), b'xyz01')
check(get(), {'format': 'raw'}, b'', b'xyz01',200, {'Content-Length': '5'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-4'})
check(get({ 'Range': 'bytes=0-4'}),{}, b'', b'xyz01',206, {'Content-Length': '5', 'Content-Range': 'bytes 0-4/5'})
check(get({ 'Range': 'bytes=1-3'}),{}, b'', b'yz0' ,206, {'Content-Length': '3', 'Content-Range': 'bytes 1-3/5'})
check(get({ 'Range': 'bytes=1-2'}),{}, b'', b'yz' ,206, {'Content-Length': '2', 'Content-Range': 'bytes 1-2/5'})
check(get({ 'Range': 'bytes=2-2'}),{}, b'', b'z' ,206, {'Content-Length': '1', 'Content-Range': 'bytes 2-2/5'})
# replace whole content via PUT without range
# (and we won't exercise GET with range routinely further)
check(put({'Content-Length': '3'}, 'abc'),{}, '', '', 204, {})
check(put({'Content-Length': '3'}, b'abc'),{}, b'', b'', 204, {})
self.assertEqual(f.getSize(), 3)
self.assertEqual(f.getData(), 'abc')
check(get(), {'format': 'raw'}, '', 'abc', 200, {'Content-Length': '3'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-2'})
self.assertEqual(f.getData(), b'abc')
check(get(), {'format': 'raw'}, b'', b'abc', 200, {'Content-Length': '3'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-2'})
# append via PUT with range (again)
check(put({'Content-Range': 'bytes 3-7/8', 'Content-Length': '5'}, 'defgh'),{}, '', '', 204, {})
check(put({'Content-Range': 'bytes 3-7/8', 'Content-Length': '5'}, b'defgh'),{}, b'', b'', 204, {})
self.assertEqual(f.getSize(), 8)
self.assertEqual(f.getData(), 'abcdefgh')
check(get(), {'format': 'raw'}, '', 'abcdefgh', 200, {'Content-Length': '8'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-7'})
self.assertEqual(f.getData(), b'abcdefgh')
check(get(), {'format': 'raw'}, b'', b'abcdefgh', 200, {'Content-Length': '8'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-7'})
# append via ._appendData() (again)
f._appendData('ij')
f._appendData(b'ij')
self.assertEqual(f.getSize(), 10)
self.assertEqual(f.getData(), 'abcdefghij')
check(get(), {'format': 'raw'}, '', 'abcdefghij', 200, {'Content-Length': '10'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-9'})
self.assertEqual(f.getData(), b'abcdefghij')
check(get(), {'format': 'raw'}, b'', b'abcdefghij', 200, {'Content-Length': '10'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-9'})
# make sure PUT with incorrect/non-append range is rejected
check(put({'Content-Range': 'bytes 10-10/10', 'Content-Length': '1'}, 'k'),{}, '', '', 400, {'X-Explanation': 'Total size unexpected'})
self.assertEqual(f.getData(), 'abcdefghij')
check(put({'Content-Range': 'bytes 10-10/11', 'Content-Length': '2'}, 'k'),{}, '', '', 400, {'X-Explanation': 'Content length unexpected'})
self.assertEqual(f.getData(), 'abcdefghij')
check(put({'Content-Range': 'bytes 8-8/10', 'Content-Length': '1'}, '?'),{}, '', '', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 9-9/10', 'Content-Length': '1'}, '?'),{}, '', '', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 9-10/11', 'Content-Length': '2'},'??'),{}, '', '', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 11-11/12', 'Content-Length': '1'}, '?'),{}, '', '', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 10-10/10', 'Content-Length': '1'}, b'k'),{}, b'', b'', 400, {'X-Explanation': 'Total size unexpected'})
self.assertEqual(f.getData(), b'abcdefghij')
check(put({'Content-Range': 'bytes 10-10/11', 'Content-Length': '2'}, b'k'),{}, b'', b'', 400, {'X-Explanation': 'Content length unexpected'})
self.assertEqual(f.getData(), b'abcdefghij')
check(put({'Content-Range': 'bytes 8-8/10', 'Content-Length': '1'}, b'?'),{}, b'', b'', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 9-9/10', 'Content-Length': '1'}, b'?'),{}, b'', b'', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 9-10/11', 'Content-Length': '2'},b'??'),{}, b'', b'', 400, {'X-Explanation': 'Can only append data'})
check(put({'Content-Range': 'bytes 11-11/12', 'Content-Length': '1'}, b'?'),{}, b'', b'', 400, {'X-Explanation': 'Can only append data'})
# TODO test 'If-Range' with GET
# TODO test multiple ranges in 'Range' with GET
......@@ -229,11 +228,11 @@ class TestBigFile(ERP5TypeTestCase):
# (called from under testBigFile_02_DataVarious driver)
def _testBigFile_02_DataVarious(self):
# BigFile's .data can be:
# str - because data comes from Data property sheet and default value is ''
# bytes - because data comes from Data property sheet and default value is b''
# None - because it can be changed
# BTreeData - because it is scalable way to work with large content
#
# str can be possibly non-empty because we could want to transparently
# bytes can be possibly non-empty because we could want to transparently
# migrate plain File documents to BigFiles.
#
# make sure BigFile correctly works in all those situations.
......@@ -242,47 +241,47 @@ class TestBigFile(ERP5TypeTestCase):
f = big_file_module.newContent(portal_type='Big File')
check = lambda *args: self.checkRequest(f, *args)
# after creation .data is '' (as per default from Data property sheet)
# after creation .data is b'' (as per default from Data property sheet)
_ = f._baseGetData()
self.assertIsInstance(_, str)
self.assertEqual(_, '')
self.assertIsInstance(_, bytes)
self.assertEqual(_, b'')
# make sure we can get empty content through all ways
# (already covered in testBigFile_01_Basic, but still)
self.assertEqual(f.getSize(), 0)
self.assertEqual(f.getData(), '')
check(get(), {'format': 'raw'}, '', '', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, '', '', 416, {'Content-Length': '0', 'Content-Range': 'bytes */0'})
self.assertEqual(f.getData(), b'')
check(get(), {'format': 'raw'}, b'', b'', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, b'', b'', 416, {'Content-Length': '0', 'Content-Range': 'bytes */0'})
# set .data to non-empty str and make sure we can get content through all ways
f._baseSetData('abc')
f._baseSetData(b'abc')
_ = f._baseGetData()
self.assertIsInstance(_, str)
self.assertEqual(_, 'abc')
self.assertIsInstance(_, bytes)
self.assertEqual(_, b'abc')
self.assertEqual(f.getSize(), 3)
self.assertEqual(f.getData(), 'abc')
check(get(), {'format': 'raw'}, 'abc', '', 200, {'Content-Length': '3'})
check(put({'Content-Range': 'bytes */*'}),{}, '' , '', R308, { 'Range': 'bytes 0-2'})
check(get({ 'Range': 'bytes=0-2'}),{}, '' , 'abc', 206, {'Content-Length': '3', 'Content-Range': 'bytes 0-2/3'})
self.assertEqual(f.getData(), b'abc')
check(get(), {'format': 'raw'}, b'abc', b'', 200, {'Content-Length': '3'})
check(put({'Content-Range': 'bytes */*'}),{}, b'' , b'', R308, { 'Range': 'bytes 0-2'})
check(get({ 'Range': 'bytes=0-2'}),{}, b'' , b'abc', 206, {'Content-Length': '3', 'Content-Range': 'bytes 0-2/3'})
# and .data should remain str after access (though later this could be
# changed to transparently migrate to BTreeData)
_ = f._baseGetData()
self.assertIsInstance(_, str)
self.assertEqual(_, 'abc')
self.assertIsInstance(_, bytes)
self.assertEqual(_, b'abc')
# on append .data should migrate to BTreeData
f._appendData('d')
f._appendData(b'd')
_ = f._baseGetData()
self.assertIsInstance(_, BTreeData)
self.assertEqual(f.getSize(), 4)
self.assertEqual(f.getData(), 'abcd')
check(get(), {'format': 'raw'}, '', 'abcd', 200, {'Content-Length': '4'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-3'})
check(get({ 'Range': 'bytes=0-3'}),{}, '', 'abcd', 206, {'Content-Length': '4', 'Content-Range': 'bytes 0-3/4'})
self.assertEqual(f.getData(), b'abcd')
check(get(), {'format': 'raw'}, b'', b'abcd', 200, {'Content-Length': '4'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-3'})
check(get({ 'Range': 'bytes=0-3'}),{}, b'', b'abcd', 206, {'Content-Length': '4', 'Content-Range': 'bytes 0-3/4'})
......@@ -292,39 +291,39 @@ class TestBigFile(ERP5TypeTestCase):
#
# see ERP5.Document.File._setSize() for details.
f._setData(None)
# NOTE still '' because it is default value specified in Data property
# NOTE still b'' because it is default value specified in Data property
# sheet for .data field
_ = f._baseGetData()
self.assertIsInstance(_, str)
self.assertEqual(_, '')
self.assertIsInstance(_, bytes)
self.assertEqual(_, b'')
# but we can change property sheet default on the fly
# XXX ( only for this particular getter _baseGetData -
# - because property type information is not stored in one place,
# but is copied on getter/setter initialization - see Getter/Setter
# in ERP5Type.Accessor.Base )
# NOTE this change is automatically reverted back in calling helper
self.assertIsInstance(f._baseGetData._default, str)
self.assertEqual(f._baseGetData._default, '')
self.assertIsInstance(f._baseGetData._default, bytes)
self.assertEqual(f._baseGetData._default, b'')
f._baseGetData.__func__._default = None # NOTE not possible to do on just f._baseGetData
self.assertIs(f._baseGetData._default, None)
self.assertIs(f._baseGetData(), None) # <- oops
self.assertEqual(f.getSize(), 0)
self.assertIs (f.getData(), None)
check(get(), {'format': 'raw'}, '', '', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, '', '', 416, {'Content-Length': '0', 'Content-Range': 'bytes */0'})
check(get(), {'format': 'raw'}, b'', b'', 200, {'Content-Length': '0'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'',R308, { 'Range': 'bytes 0--1'}) # XXX 0--1 ok?
check(get({ 'Range': 'bytes=0-0'}),{}, b'', b'', 416, {'Content-Length': '0', 'Content-Range': 'bytes */0'})
# on append .data should become BTreeData
f._appendData('x')
f._appendData(b'x')
_ = f._baseGetData()
self.assertIsInstance(_, BTreeData)
self.assertEqual(f.getSize(), 1)
self.assertEqual(f.getData(), 'x')
check(get(), {'format': 'raw'}, '', 'x', 200, {'Content-Length': '1'})
check(put({'Content-Range': 'bytes */*'}),{}, '', '', R308, { 'Range': 'bytes 0-0'})
check(get({ 'Range': 'bytes=0-3'}),{}, '', 'x', 206, {'Content-Length': '1', 'Content-Range': 'bytes 0-0/1'})
self.assertEqual(f.getData(), b'x')
check(get(), {'format': 'raw'}, b'', b'x', 200, {'Content-Length': '1'})
check(put({'Content-Range': 'bytes */*'}),{}, b'', b'', R308, { 'Range': 'bytes 0-0'})
check(get({ 'Range': 'bytes=0-3'}),{}, b'', b'x', 206, {'Content-Length': '1', 'Content-Range': 'bytes 0-0/1'})
......@@ -333,26 +332,26 @@ class TestBigFile(ERP5TypeTestCase):
big_file_module = self.getPortal().big_file_module
f = big_file_module.newContent(portal_type='Big File')
# Data property sheet specifies .data default to ''
# Data property sheet specifies .data default to b''
_ = f._baseGetData()
self.assertIsInstance(_, str)
self.assertEqual(_, '')
self.assertIsInstance(_, bytes)
self.assertEqual(_, b'')
# NOTE obtaining getter is not possible via BigFile._baseGetData
g = f._baseGetData.__func__
self.assertIsInstance(g._default, str)
self.assertEqual(g._default, '')
self.assertIsInstance(g._default, bytes)
self.assertEqual(g._default, b'')
try:
self._testBigFile_02_DataVarious()
# restore ._baseGetData._default and make sure restoration really worked
finally:
g._default = ''
g._default = b''
f._baseSetData(None) # so that we are sure getter returns class defaults
_ = f._baseGetData()
self.assertIsInstance(_, str)
self.assertEqual(_, '')
self.assertIsInstance(_, bytes)
self.assertEqual(_, b'')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment