[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