[Zope3-checkins]
SVN: Zope3/branches/jhauser-filefieldwidget/src/zope/
Refactoring of File class
Roger Ineichen
roger at projekt01.ch
Thu Jan 20 21:21:56 EST 2005
Log message for revision 28902:
Refactoring of File class
o Move Mime field tests to a own file
o Added temp TODO.txt to zope.app.file
o Old tests are running and Zope is starting again.
o No BBB at this time
Changed:
A Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/TODO.txt
U Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
U Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py
A Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_mimefield.py
U Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py
-=-
Added: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/TODO.txt
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/TODO.txt 2005-01-20 22:59:46 UTC (rev 28901)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/TODO.txt 2005-01-21 02:21:56 UTC (rev 28902)
@@ -0,0 +1,26 @@
+====
+TODO
+====
+
+- Check constructor and mutator
+
+- Implement Mime widgets
+
+- Add new widget tests
+
+- Refactor IImage, I didn't look at it till now, but the test runs well. ;-)
+
+- Check external editor, they use the class FileReadFile for
+ the adapter ReadFileAdapter. The class FileReadFile isn't used
+ anymore for the File implementation.
+
+- Add Mime field tests in zope.schema.tests.test_mimefield.py
+ Just copy/paste old test.
+
+- Check and add backward compatibility
+
+- Move old tests to own section and mark them for BBB
+
+- Write a TODO for removing BBB, check if all BBB methods are marked with comments
+
+- Add deprication warnings
Property changes on: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/TODO.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py 2005-01-20 22:59:46 UTC (rev 28901)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py 2005-01-21 02:21:56 UTC (rev 28902)
@@ -19,92 +19,105 @@
from persistent import Persistent
from transaction import get_transaction
+
from zope.interface import implements
-
from zope.publisher.browser import FileUpload
-from interfaces import IMime, IFile, IFileContent
+from zope.app.file.interfaces import IMime, IFile, IFileStorage, IFileContent
+
# set the size of the chunks
MAXCHUNKSIZE = 1 << 16
-class Mime(Persistent):
- """A persistent content component storing binary file data
- Let's test the constructor:
+class FileChunk(Persistent):
+ """Wrapper for possibly large data"""
- >>> file = Mime()
- >>> file.contentType
- ''
- >>> file.data
- ''
+ next = None
- >>> file = Mime('Foobar')
- >>> file.contentType
- ''
- >>> file.data
- 'Foobar'
+ def __init__(self, data):
+ self._data = data
- >>> file = Mime('Foobar', 'text/plain')
- >>> file.contentType
- 'text/plain'
- >>> file.data
- 'Foobar'
+ def __getslice__(self, i, j):
+ return self._data[i:j]
- >>> file = Mime(data='Foobar', contentType='text/plain')
- >>> file.contentType
- 'text/plain'
- >>> file.data
- 'Foobar'
+ def __len__(self):
+ data = str(self)
+ return len(data)
+ def __str__(self):
+ next = self.next
+ if next is None:
+ return self._data
- Let's test the mutators:
+ result = [self._data]
+ while next is not None:
+ self = next
+ result.append(self._data)
+ next = self.next
- >>> file = Mime()
- >>> file.contentType = 'text/plain'
- >>> file.contentType
- 'text/plain'
+ return ''.join(result)
- >>> file.data = 'Foobar'
- >>> file.data
+
+class FileStorage(Persistent):
+ """A persistent storage storing binary file data
+ Let's test the constructor:
+
+ >>> storage = FileStorage()
+ >>> str(storage._data)
+ ''
+
+ >>> storage = FileStorage('Foobar')
+ >>> str(storage._data)
'Foobar'
- >>> file.data = None
+ >>> storage = FileStorage(None)
Traceback (most recent call last):
...
TypeError: Cannot set None data on a file.
+ Let's test read method:
+ >>> storage = FileStorage('Foobar')
+ >>> storage.read()
+ 'Foobar'
+
+ Let's test write method:
+
+ >>> storage.write('Foobar'*2)
+ >>> str(storage._data)
+ 'FoobarFoobar'
+
Let's test large data input:
- >>> file = Mime()
+ >>> storage = FileStorage()
Insert as string:
- >>> file.data = 'Foobar'*60000
- >>> file.getSize()
+ >>> storage.write('Foobar'*60000)
+ >>> storage.getSize()
360000
- >>> file.data == 'Foobar'*60000
+ >>> str(storage._data) == 'Foobar'*60000
True
Insert data as FileChunk:
>>> fc = FileChunk('Foobar'*4000)
- >>> file.data = fc
- >>> file.getSize()
+ >>> storage.write(fc)
+ >>> storage.getSize()
24000
- >>> file.data == 'Foobar'*4000
+ >>> str(storage._data) == 'Foobar'*4000
True
- Insert data from file object:
+ Insert data from storage object:
>>> import cStringIO
>>> sio = cStringIO.StringIO()
>>> sio.write('Foobar'*100000)
>>> sio.seek(0)
- >>> file.data = sio
- >>> file.getSize()
+ >>> storage.write(sio)
+ >>> storage.getSize()
600000
- >>> file.data == 'Foobar'*100000
+ >>> str(storage._data) == 'Foobar'*100000
True
@@ -116,21 +129,21 @@
>>> verifyClass(IMime, Mime)
True
"""
-
- implements(IMime)
- def __init__(self, data='', contentType=''):
- self.data = data
- self.contentType = contentType
+ implements(IFileStorage)
- def _getData(self):
+ def __init__(self, data=''):
+ self._data = None
+ self._size = None
+ self.write(data)
+
+ def read(self):
if isinstance(self._data, FileChunk):
return str(self._data)
else:
return self._data
- def _setData(self, data):
- # XXX can probably be removed
+ def write(self, data):
# Handle case when data is a string
if isinstance(data, unicode):
data = data.encode('UTF-8')
@@ -211,13 +224,196 @@
return
def getSize(self):
- '''See `IFile`'''
return self._size
- def open(self, mode='r'):
- pass
+class Mime(Persistent):
+ """A persistent content component storing binary file data
+
+ Let's test the constructor:
+
+ >>> mime = Mime()
+ >>> mime.data
+ ''
+ >>> mime.contentType
+ ''
+ >>> mime.encoding == None
+ True
+
+ >>> mime = Mime('Foobar')
+ >>> mime.data
+ 'Foobar'
+ >>> mime.contentType
+ ''
+ >>> mime.encoding == None
+ True
+
+ >>> mime = Mime('Foobar', 'text/plain')
+ >>> mime.data
+ 'Foobar'
+ >>> mime.contentType
+ 'text/plain'
+ >>> mime.encoding == None
+ True
+
+ >>> mime = Mime(data='Foobar', contentType='text/plain')
+ >>> mime.data
+ 'Foobar'
+ >>> mime.encoding == None
+ True
+
+ >>> mime = Mime('Foobar', 'text/plain', 'UTF-8')
+ >>> mime.data
+ 'Foobar'
+ >>> mime.contentType
+ 'text/plain'
+ >>> mime.encoding
+ 'UTF-8'
+
+ >>> mime = Mime(data='Foobar', contentType='text/plain', encoding='UTF-8')
+ >>> mime.data
+ 'Foobar'
+ >>> mime.contentType
+ 'text/plain'
+ >>> mime.encoding
+ 'UTF-8'
+
+
+ Let's test the mutators:
+
+ >>> mime.data = 'Foobar'
+ >>> mime.data
+ 'Foobar'
+
+ >>> mime = Mime()
+ >>> mime.contentType = 'text/plain'
+ >>> mime.contentType
+ 'text/plain'
+
+ >>> mime = Mime()
+ >>> mime.encoding = 'UTF-8'
+ >>> mime.encoding
+ 'UTF-8'
+
+ >>> mime.data = None
+ Traceback (most recent call last):
+ ...
+ TypeError: Cannot set None data on a file.
+
+
+ Let's test large data input:
+
+ >>> mime = Mime()
+
+ Insert as string:
+
+ >>> mime.data = 'Foobar'*60000
+ >>> mime.getSize()
+ 360000
+ >>> mime.data == 'Foobar'*60000
+ True
+
+ Insert data as FileChunk:
+
+ >>> fc = FileChunk('Foobar'*4000)
+ >>> mime.data = fc
+ >>> mime.getSize()
+ 24000
+ >>> mime.data == 'Foobar'*4000
+ True
+
+ Insert data from file object:
+
+ >>> import cStringIO
+ >>> sio = cStringIO.StringIO()
+ >>> sio.write('Foobar'*100000)
+ >>> sio.seek(0)
+ >>> mime.data = sio
+ >>> mime.getSize()
+ 600000
+ >>> mime.data == 'Foobar'*100000
+ True
+
+
+ Test open(mode='r') method:
+
+ >>> mime = Mime('Foobar')
+ >>> file = mime.open('r')
+ >>> file.read()
+ 'Foobar'
+
+ >>> file.size()
+ 6
+
+ >>> file.write('sometext')
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'ReadFileStorage' object has no attribute 'write'
+
+ >>> file = mime.open(mode='w')
+ >>> file.write('Foobar'*2)
+ >>> mime.data
+ 'FoobarFoobar'
+
+ >>> file.read()
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'WriteFileStorage' object has no attribute 'read'
+
+ Last, but not least, verify the interface:
+
+ >>> from zope.interface.verify import verifyClass
+ >>> IMime.implementedBy(Mime)
+ True
+ >>> verifyClass(IMime, Mime)
+ True
+ """
+
+ implements(IMime)
+
+ def __init__(self, data='', contentType='', encoding=None):
+ self._data = FileStorage()
+ self._data.write(data)
+ self._contentType = contentType
+ self._encoding = encoding
+
+ def _getData(self):
+ # TODO: shold we read via the open() method, not really? ri
+ return self._data.read()
+
+ def _setData(self, data):
+ # TODO: shold we write via the open() method, not really? ri
+ self._data.write(data)
+
data = property(_getData, _setData)
+
+ def _getContentType(self):
+ return self._contentType
+
+ def _setContentType(self, contentType):
+ self._contentType = contentType
+
+ contentType = property(_getContentType, _setContentType)
+
+ def _getEncoding(self):
+ return self._encoding
+
+ def _setEncoding(self, encoding):
+ self._encoding = encoding
+
+ encoding = property(_getEncoding, _setEncoding)
+
+ def getSize(self):
+ return self._data.getSize()
+
+ def open(self, mode='r'):
+ if mode == 'r':
+ return ReadFileStorage(self._data)
+ if mode == 'w':
+ return WriteFileStorage(self._data)
+ else:
+ pass
+ # TODO: raise wrong file open attribute error
class File(Persistent):
@@ -315,72 +511,80 @@
self.contentType = contentType
# old compatibility methods
- def _getData(self):
- if isinstance(self.contents._data, FileChunk):
- # XXX here we loose our memory efficient handling
- return str(self.contents._data)
- else:
- return self.contents._data
+ def _getContents(self):
+ return self.contents.data
- def _setData(self, data):
+ def _setContents(self, data):
self.contents.data = data
def open(self, mode='r'):
"""return a file-like object for reading or updating the file value.
"""
- pass
+ if mode == 'r':
+ return self.contents.open(mode='r')
+ if mode == 'w':
+ return self.contents.open(mode='w')
+ else:
+ pass
+ # TODO: raise wrong file open attribute error
-## Leads to maximum recursion erro ?
-## # new access to file data
-## def _getContents(self):
-## return self.contents.open()
-
-## def _setContents(self, data):
-## self.contents = Mime()
-## contents = getattr(self, 'contents')
-## contents.data = data
-## return
-
-## contents = property(_getContents, _setContents)
-
def getSize(self):
return self.contents.getSize()
# See IFile.
- data = property(_getData, _setData)
+ content = property(_getContents, _setContents)
+
+ # BBB: remove it after removing BBB
+ # old compatibility methods
+ def _getData(self):
+ return self.contents.data
+ def _setData(self, data):
+ self.contents.data = data
-class FileChunk(Persistent):
- """Wrapper for possibly large data"""
+ data = property(_getContents, _setContents)
- next = None
- def __init__(self, data):
- self._data = data
- def __getslice__(self, i, j):
- return self._data[i:j]
+class ReadFileStorage(object):
+ """Adapter for file-system style read access.
- def __len__(self):
- data = str(self)
- return len(data)
+ >>> content = "This is some file\\ncontent."
+ >>> filestorage = FileStorage(content)
+ >>> filestorage._data = content
+ >>> ReadFileStorage(filestorage).read() == content
+ True
+ >>> ReadFileStorage(filestorage).size() == len(content)
+ True
+ """
+ def __init__(self, context):
+ self.__context = context
- def __str__(self):
- next = self.next
- if next is None:
- return self._data
+ def read(self):
+ return self.__context.read()
- result = [self._data]
- while next is not None:
- self = next
- result.append(self._data)
- next = self.next
+ def size(self):
+ return len(self.__context.read())
- return ''.join(result)
+class WriteFileStorage(object):
+ """Adapter for file-system style write access.
+ >>> content = "This is some file\\ncontent."
+ >>> filestorage = FileStorage(content)
+ >>> WriteFileStorage(filestorage).write(content)
+ >>> str(filestorage._data) == content
+ True
+ """
+ def __init__(self, context):
+ self.__context = context
+
+ def write(self, data):
+ self.__context.write(data)
+
+
class FileReadFile(object):
- '''Adapter for file-system style read access.
+ """Adapter for file-system style read access.
>>> file = File()
>>> content = "This is some file\\ncontent."
@@ -390,7 +594,7 @@
True
>>> FileReadFile(file).size() == len(content)
True
- '''
+ """
def __init__(self, context):
self.context = context
Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py 2005-01-20 22:59:46 UTC (rev 28901)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py 2005-01-21 02:21:56 UTC (rev 28902)
@@ -22,6 +22,29 @@
from zope.interface import Interface
from zope.app.i18n import ZopeMessageIDFactory as _
+# BBB:
+from zope.schema import BytesLine
+
+
+class IFileStorage(Interface):
+ """IFileStorage provides a read and write method for file-based objects.
+
+ Objects implementing this interface handle the storage of the file
+ data. Actually we provide a string implementation for smaller files
+ and a FileChunk for larger files. Other storage for store a file
+ blob to a SQL-Server are possible implementations.
+ """
+
+ def read():
+ """Read the file data."""
+
+ def write(data):
+ """Write the file data."""
+
+ def getSize():
+ """Returns the size of the file data."""
+
+
class IMime(Interface):
# TODO: remove the line below
@@ -48,7 +71,7 @@
#data = Bytes(
data = MimeData (
title=_(u'Data'),
- description=_(u'The actual content of the object.'),
+ description=_(u'The actual content of the file.'),
default='',
missing_value='',
required=False,
@@ -64,11 +87,12 @@
"""
-class IFile(IMime):
+class IFile(Interface):
- contents = Mime(
- title = _(u'The mime data'),
- description = _(u'The mime data, which can be read as a file.'),
+ content = Mime(
+ title = _(u'The file data'),
+ description = _(u'The mime information and file data, which can be '
+ 'read as a file.'),
default=None,
missing_value=None,
required=False,
@@ -83,6 +107,17 @@
required=False,
)
+ # BBB: remove contentType
+ # this is explicit requiered for permission reason, old classes use the
+ # interface IFile for permission settings
+ contentType = BytesLine(
+ title = _(u'Content Type'),
+ description=_(u'The content type identifies the type of data.'),
+ default='',
+ required=False,
+ missing_value=''
+ )
+
def getSize():
"""Return the byte-size of the data of the object."""
Added: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_mimefield.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_mimefield.py 2005-01-20 22:59:46 UTC (rev 28901)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_mimefield.py 2005-01-21 02:21:56 UTC (rev 28902)
@@ -0,0 +1,106 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""String field tests
+
+$Id: test_strfield.py 28865 2005-01-18 22:29:04Z jhauser $
+"""
+from unittest import TestSuite, main, makeSuite
+from zope.schema import Bytes, Mime, BytesLine, Text, TextLine
+from zope.schema.interfaces import ValidationError
+from zope.schema.interfaces import RequiredMissing, InvalidValue
+from zope.schema.interfaces import TooShort, TooLong, ConstraintNotSatisfied
+from zope.schema.tests.test_field import FieldTestBase
+
+
+#class MimeTest(StrTest, MultiLine):
+# _Field_Factory = Mime
+# _convert = str
+# dummy_file = open('__init__.py','r')
+#
+# def testValidateFile(self):
+# field = self._Field_Factory()
+# field.validate(self.dummy_file)
+
+class MimeTest(FieldTestBase):
+ """Test the Mime Field."""
+
+ def testValidate(self):
+ field = self._Field_Factory(title=u'Str field', description=u'',
+ readonly=False, required=False)
+ field.validate(None)
+ field.validate(self._convert('foo'))
+ field.validate(self._convert(''))
+
+ def testValidateRequired(self):
+
+ # Note that if we want to require non-empty strings,
+ # we need to set the min-length to 1.
+
+ field = self._Field_Factory(
+ title=u'Str field', description=u'',
+ readonly=False, required=True, min_length=1)
+ field.validate(self._convert('foo'))
+
+ self.assertRaises(RequiredMissing, field.validate, None)
+ self.assertRaises(TooShort, field.validate, self._convert(''))
+
+ def testValidateMinLength(self):
+ field = self._Field_Factory(
+ title=u'Str field', description=u'',
+ readonly=False, required=False, min_length=3)
+ field.validate(None)
+ field.validate(self._convert('333'))
+ field.validate(self._convert('55555'))
+
+ self.assertRaises(TooShort, field.validate, self._convert(''))
+ self.assertRaises(TooShort, field.validate, self._convert('22'))
+ self.assertRaises(TooShort, field.validate, self._convert('1'))
+
+ def testValidateMaxLength(self):
+ field = self._Field_Factory(
+ title=u'Str field', description=u'',
+ readonly=False, required=False, max_length=5)
+ field.validate(None)
+ field.validate(self._convert(''))
+ field.validate(self._convert('333'))
+ field.validate(self._convert('55555'))
+
+ self.assertRaises(TooLong, field.validate, self._convert('666666'))
+ self.assertRaises(TooLong, field.validate, self._convert('999999999'))
+
+ def testValidateMinLengthAndMaxLength(self):
+ field = self._Field_Factory(
+ title=u'Str field', description=u'',
+ readonly=False, required=False,
+ min_length=3, max_length=5)
+
+ field.validate(None)
+ field.validate(self._convert('333'))
+ field.validate(self._convert('4444'))
+ field.validate(self._convert('55555'))
+
+ self.assertRaises(TooShort, field.validate, self._convert('22'))
+ self.assertRaises(TooShort, field.validate, self._convert('22'))
+ self.assertRaises(TooLong, field.validate, self._convert('666666'))
+ self.assertRaises(TooLong, field.validate, self._convert('999999999'))
+
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(MimeTest),
+ ))
+
+if __name__ == '__main__':
+ main(defaultTest='test_suite')
Property changes on: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_mimefield.py
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py 2005-01-20 22:59:46 UTC (rev 28901)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/tests/test_strfield.py 2005-01-21 02:21:56 UTC (rev 28902)
@@ -101,15 +101,6 @@
field = self._Field_Factory()
self.assertRaises(ValidationError, field.validate, u'hello')
-class MimeTest(StrTest, MultiLine):
- _Field_Factory = Mime
- _convert = str
- dummy_file = open('__init__.py','r')
-
- def testValidateFile(self):
- field = self._Field_Factory()
- field.validate(self.dummy_file)
-
class TextTest(StrTest, MultiLine):
_Field_Factory = Text
def _convert(self, v):
@@ -137,7 +128,6 @@
def test_suite():
return TestSuite((
makeSuite(BytesTest),
- makeSuite(MimeTest),
makeSuite(TextTest),
makeSuite(LineTest),
makeSuite(TextLineTest),
More information about the Zope3-Checkins
mailing list