[Zope3-checkins] SVN: Zope3/branches/jhauser-filefieldwidget/src/zope/ Added different sub interface for prevent double definition in IFile.

Roger Ineichen roger at projekt01.ch
Sat Jan 22 20:59:08 EST 2005


Log message for revision 28916:
  Added different sub interface for prevent double definition in IFile.
  If we implement IMime directly in IFile, we get the fields contentType and encoding twice.
  One time in the Mime object wich is a subobject rendered in a separate widget and one time
  in the IFile obeject itself.
  
  TODO: check Schema wwidget, there is still a bug in the fileupload form.
  Could be a proxy error.

Changed:
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/configure.zcml
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/ftests.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py

-=-
Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/configure.zcml
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/configure.zcml	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/configure.zcml	2005-01-23 01:59:08 UTC (rev 28916)
@@ -34,7 +34,7 @@
       menu="zmi_views" title="Text Edit"
       for="zope.app.file.interfaces.IFile"
       action="fileedit.html"
-      filter="python:context.contents.contentType.startswith('text/')"
+      filter="python:context.contents.mimeType.startswith('text/')"
       permission="zope.ManageContent" />
 
   <browser:editform

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/ftests.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/ftests.py	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/browser/ftests.py	2005-01-23 01:59:08 UTC (rev 28916)
@@ -51,6 +51,7 @@
             '/+/zope.app.file.File=',
             form={'type_name': u'zope.app.file.File',
                   'field.data': StringIO('A file'),
+                  'field.contentType': '',
                   'add_input_name': u'file',
                   'UPDATE_SUBMIT': u'Add'},
             basic='mgr:mgrpw')

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/configure.zcml	2005-01-23 01:59:08 UTC (rev 28916)
@@ -59,7 +59,7 @@
 
     <require
         permission="zope.View"
-        interface=".interfaces.IReadFileAccess"
+        interface=".interfaces.IReadFileStorage"
         />
 
   </class>
@@ -68,7 +68,7 @@
 
     <require
         permission="zope.ManageContent"
-        interface=".interfaces.IWriteFileAccess"
+        interface=".interfaces.IWriteFileStorage"
         />
 
   </class>

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py	2005-01-23 01:59:08 UTC (rev 28916)
@@ -26,7 +26,10 @@
 from zope.publisher.browser import FileUpload
 from zope.security.proxy import removeSecurityProxy
 
-from zope.app.file.interfaces import IMime, IFile, IFileStorage, IFileContent
+from zope.app.file.interfaces import IReadFileStorage, IWriteFileStorage
+from zope.app.file.interfaces import IFileStorage
+from zope.app.file.interfaces import IReadMime, IWriteMime, IMime
+from zope.app.file.interfaces import IFile, IFileContent
 
 # TODO: remove it, just for testing
 from zope.proxy import isProxy
@@ -233,7 +236,9 @@
     def getSize(self):
         return self._size
 
+    size = getSize
 
+
 class Mime(Persistent):
     """A persistent content component storing binary file data
 
@@ -242,45 +247,45 @@
     >>> mime = Mime()
     >>> mime.data
     ''
-    >>> mime.contentType
+    >>> mime.mimeType
     ''
-    >>> mime.encoding == None
+    >>> mime.encoding == ''
     True
 
     >>> mime = Mime('Foobar')
     >>> mime.data
     'Foobar'
-    >>> mime.contentType
+    >>> mime.mimeType
     ''
-    >>> mime.encoding == None
+    >>> mime.encoding == ''
     True
 
     >>> mime = Mime('Foobar', 'text/plain')
     >>> mime.data
     'Foobar'
-    >>> mime.contentType
+    >>> mime.mimeType
     'text/plain'
-    >>> mime.encoding == None
+    >>> mime.encoding == ''
     True
 
-    >>> mime = Mime(data='Foobar', contentType='text/plain')
+    >>> mime = Mime(data='Foobar', mimeType='text/plain')
     >>> mime.data
     'Foobar'
-    >>> mime.encoding == None
+    >>> mime.encoding == ''
     True
 
     >>> mime = Mime('Foobar', 'text/plain', 'UTF-8')
     >>> mime.data
     'Foobar'
-    >>> mime.contentType
+    >>> mime.mimeType
     'text/plain'
     >>> mime.encoding
     'UTF-8'
 
-    >>> mime = Mime(data='Foobar', contentType='text/plain', encoding='UTF-8')
+    >>> mime = Mime(data='Foobar', mimeType='text/plain', encoding='UTF-8')
     >>> mime.data
     'Foobar'
-    >>> mime.contentType
+    >>> mime.mimeType
     'text/plain'
     >>> mime.encoding
     'UTF-8'
@@ -293,8 +298,8 @@
     'Foobar'
 
     >>> mime = Mime()
-    >>> mime.contentType = 'text/plain'
-    >>> mime.contentType
+    >>> mime.mimeType = 'text/plain'
+    >>> mime.mimeType
     'text/plain'
 
     >>> mime = Mime()
@@ -317,7 +322,7 @@
     >>> mime.data = 'Foobar'*60000
     >>> mime.getSize()
     360000
-    >>> mime.data == 'Foobar'*60000
+    >>> mime.open().read() == 'Foobar'*60000
     True
 
     Insert data as FileChunk:
@@ -359,7 +364,7 @@
 
     >>> file = mime.open(mode='w')
     >>> file.write('Foobar'*2)
-    >>> mime.data
+    >>> mime.open().read()
     'FoobarFoobar'
 
     >>> file.read()
@@ -378,42 +383,45 @@
     
     implements(IMime)
 
-    def __init__(self, data='', contentType='', encoding=None):
-        self._data = FileStorage()
-        self._data.write(data)
-        self._contentType = contentType
-        self._encoding = encoding
+    def __init__(self, data='', mimeType='', encoding=''):
+        self._data = FileStorage(data)
+        self.mimeType = mimeType
+        self.encoding = encoding
 
-    def _getData(self):
-        # TODO: shold we read via the open() method, not really? ri
-        file = self._data.read()
-        return file
+    # IMime
+    mimeType = FieldProperty(IMime['mimeType'])
 
+    encoding = FieldProperty(IMime['encoding'])
+
+    def _getData(self):
+        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
+    # IReadMime
+    def getMimeType(self):
+        return self.mimeType
 
-    def _setContentType(self, contentType):
-        self._contentType = contentType
+    # IWriteMime
+    def setMimeType(self, mimeType):
+        self.mimeType = mimeType
 
-    contentType = property(_getContentType, _setContentType)
+    # IReadMime
+    def getEncoding(self):
+        return self.encoding
 
-    def _getEncoding(self):
-        return self._encoding
+    # IWriteMime
+    def setEncoding(self, encoding):
+        self.encoding = encoding
 
-    def _setEncoding(self, encoding):
-        self._encoding = encoding
-
-    encoding = property(_getEncoding, _setEncoding)
-
+    # IReadMime
     def getSize(self):
         return self._data.getSize()
 
+    # IMime
     def open(self, mode='r'):
         if mode == 'r':
             return ReadFileStorage(self._data)
@@ -557,7 +565,6 @@
     Let's test large data input for BBB files:
 
     >>> file = File()
-    >>> file._contents = None
 
     Insert as string:
 
@@ -590,163 +597,68 @@
 
     """
 
-    implements(IFile, IMime, IFileContent)
-    
-    # BBB: set the _contents = None, if we have a Mime object stored
-    # under the _contents attr, we have a new style file.
-    # I f we test old style files we have to set the _contents to None 
-    # after initializing
-    _contents = None
-    
+    implements(IFile, IReadMime, IWriteMime, IFileContent)
+
     def __init__(self, data='', contentType=''):
-        self._contents = Mime()
-        self.open(mode='w').write(data)
-        
-        # BBB: map contentType to the right value for new style file
-        self.contentType = contentType
-        self.data = data
-        
-    def isNewStyle(self):
-        if self._contents is None:
-            warn("The File implementation has ben changes, migrate your class",
-                DeprecationWarning, 2)
-            return False
-        else:
-            return True
+        self.contents = Mime(data, contentType)
 
-    # TODO: Fix the widgets for to store the data
-    # now we get a Mime instance form the widget, but _get/_setContents 
-    # points to _contents.data
-    # We have to change this, that we can set on the contents attribute 
-    # directly a Mime instance.
-    # But how should we access the file data? Only with the open method?
-    # This whould break everything... hm, perhaps we can use the data property 
-    # for BBB and a access directly to the file data.
-    def _getContents(self):
-        return self._contents
+    contents = FieldProperty(IFile['contents'])
 
-    def _setContents(self, contents):
-        self._contents = contents
+    def getMimeType(self):
+        return self.contents.mimeType
 
+    def setMimeType(self, mimeType):
+        self.contents.mimeType = mimeType
+
+    def getEncoding(self):
+        return self.contents.encoding
+
+    def setEncoding(self, encoding):
+        self.contents.encoding = encoding
+
+    def getSize(self):
+        return self.contents.getSize()
+
     # TODO: How can we integrate security in this method
     # We must support read and wirte permission on this method
     def open(self, mode='r'):
         """return a file-like object for reading or updating the file value.
         """
         if mode == 'r':
-            return self._contents.open(mode='r')
+            return self.contents.open(mode='r')
         if mode == 'w':
-            return self._contents.open(mode='w')
+            return self.contents.open(mode='w')
         else:
             pass
             # TODO: raise wrong file open attribute error
 
-    # BBB: supports BBB
-    def getSize(self):
-        if self.isNewStyle():
-            return self._contents.getSize()
-        else:
-            warn("The File implementation has ben changes, migrate your class",
-                DeprecationWarning, 2)
-            return self._size
-    
-    # See IFile.
-    contents = property(_getContents, _setContents)
-    #contents = FieldProperty(IFile['contents'])
-    
-    # BBB: remove it after removing BBB
-    # TODO: add deprication warning
-    def _getData(self):
-        warn("The data attribute is deprecated, migrate your File class",
+
+    # BBB: the attribute contentType is deprecated.
+    def _getContentType(self):
+        warn("The contentType attribute is deprecated, use getMimeType()",
             DeprecationWarning, 2)
-        if isinstance(self._data, FileChunk):
-            return str(self._data)
-        else:
-            return self._data
+        return self.contents.mimeType
 
-    # TODO: add deprication warning
-    def _setData(self, data):
-        # Handle case when data is a string
-        warn("The data attribute is deprecated, migrate your File class",
+    def _setContentType(self, contentType):
+        warn("The contentType attribute is deprecated, use setMimeType()",
             DeprecationWarning, 2)
-        if isinstance(data, unicode):
-            data = data.encode('UTF-8')
+        self.contents.mimeType = contentType
+    
+    # BBB: remove the code below this line if removing BBB
+    # since we store the contentType attriute on the File instance
+    # there is no need for a property contentType, use setMimeType
+    # and getMimeType instead. We use the contentType property in the Mime
+    # instance in forms directly via the Schema field of the contents
+    # attribute.
+    contentType = property(_getContentType, _setContentType)
 
-        if isinstance(data, str):
-            self._data, self._size = FileChunk(data), len(data)
-            return
+    def _getData(self):
+        return self.open(mode='r').read()
+        
 
-        # Handle case when data is None
-        if data is None:
-            raise TypeError('Cannot set None data on a file.')
+    def _setData(self, data):
+        self.open(mode='w').write(data)
 
-        # Handle case when data is already a FileChunk
-        if isinstance(data, FileChunk):
-            size = len(data)
-            self._data, self._size = data, size
-            return
-
-        # Handle case when data is a file object
-        seek = data.seek
-        read = data.read
-
-        seek(0, 2)
-        size = end = data.tell()
-
-        if size <= 2*MAXCHUNKSIZE:
-            seek(0)
-            if size < MAXCHUNKSIZE:
-                self._data, self._size = read(size), size
-                return
-            self._data, self._size = FileChunk(read(size)), size
-            return
-
-        # Make sure we have an _p_jar, even if we are a new object, by
-        # doing a sub-transaction commit.
-        get_transaction().commit(1)
-
-        jar = self._p_jar
-
-        if jar is None:
-            # Ugh
-            seek(0)
-            self._data, self._size = FileChunk(read(size)), size
-            return
-
-        # Now we're going to build a linked list from back
-        # to front to minimize the number of database updates
-        # and to allow us to get things out of memory as soon as
-        # possible.
-        next = None
-        while end > 0:
-            pos = end - MAXCHUNKSIZE
-            if pos < MAXCHUNKSIZE:
-                pos = 0 # we always want at least MAXCHUNKSIZE bytes
-            seek(pos)
-            data = FileChunk(read(end - pos))
-
-            # Woooop Woooop Woooop! This is a trick.
-            # We stuff the data directly into our jar to reduce the
-            # number of updates necessary.
-            jar.add(data)
-
-            # This is needed and has side benefit of getting
-            # the thing registered:
-            data.next = next
-
-            # Now make it get saved in a sub-transaction!
-            get_transaction().commit(1)
-
-            # Now make it a ghost to free the memory.  We
-            # don't need it anymore!
-            data._p_changed = None
-
-            next = data
-            end = pos
-
-        self._data, self._size = next, size
-        return
-
     data = property(_getData, _setData)
 
 
@@ -757,11 +669,28 @@
     >>> content = "This is some file\\ncontent."
     >>> filestorage = FileStorage(content)
     >>> filestorage._data = content
+
+    Test the read method:
+
     >>> ReadFileStorage(filestorage).read() == content
     True
+
+    Test the lenght:
+
     >>> ReadFileStorage(filestorage).size() == len(content)
     True
+
+    Last, but not least, verify the interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> IReadFileStorage.implementedBy(ReadFileStorage)
+    True
+    >>> verifyClass(IReadFileStorage, ReadFileStorage)
+    True
     """
+
+    implements(IReadFileStorage)
+
     def __init__(self, context):
         self.__context = context
 
@@ -771,16 +700,32 @@
     def size(self):
         return len(self.__context.read())
 
+    getSize = size
 
+
 class WriteFileStorage(object):
     """Adapter for file-system style write access.
 
     >>> content = "This is some file\\ncontent."
     >>> filestorage = FileStorage(content)
+
+    Test the write method:
+
     >>> WriteFileStorage(filestorage).write(content)
     >>> str(filestorage._data) == content
     True
+
+    Last, but not least, verify the interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> IWriteFileStorage.implementedBy(WriteFileStorage)
+    True
+    >>> verifyClass(IWriteFileStorage, WriteFileStorage)
+    True
     """
+
+    implements(IWriteFileStorage)
+
     def __init__(self, context):
         self.__context = context
 
@@ -788,6 +733,7 @@
         self.__context.write(data)
 
 
+# TODO, this is used in external editor, check if we can replace it.
 class FileReadFile(object):
     """Adapter for file-system style read access.
 

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/interfaces.py	2005-01-23 01:59:08 UTC (rev 28916)
@@ -27,7 +27,27 @@
 from zope.schema import BytesLine
 
 
-class IFileStorage(Interface):
+class IReadFileStorage(Interface):
+    """File read interface."""
+
+    def read():
+        """Read access on file."""
+
+    def size():
+        """Size of the file."""
+
+    def getSize():
+        """Size of the file."""
+
+
+class IWriteFileStorage(Interface):
+    """File write interface."""
+
+    def write(data):
+        """Write access on file."""
+
+
+class IFileStorage(IReadFileStorage, IWriteFileStorage):
     """IFileStorage provides a read and write method for file-based objects.
     
     Objects implementing this interface handle the storage of the file
@@ -36,40 +56,49 @@
     blob to a SQL-Server are possible implementations.
     """
 
-    def read():
-        """Read the file data."""
 
-    def write(data):
-        """Write the file data."""
+class IReadMime(Interface):
+    """Mime read interface."""
 
     def getSize():
-        """Returns the size of the file data."""
+        """Return the byte-size of the data of the object."""
 
+    def getMimeType():
+        """Return the mime-type of the file."""
 
-class IMime(Interface):
+    def getEncoding():
+        """Return the encoding of a text-based file."""
 
-    # TODO: remove the line below
-    # contentType = BytesLine(
-    contentType = MimeType(
+
+class IWriteMime(Interface):
+    """Mime write interface."""
+
+    def setMimeType(mimeType):
+        """Set the mime-type for the file object."""
+
+    def setEncoding(mimeType):
+        """Set the encoding for text-based file object."""
+
+
+class IMime(IReadMime, IWriteMime):
+    """Mime interface."""
+
+    mimeType = MimeType(
         title = _(u'Content Type'),
-        description=_(u'The content type identifies the type of data.'),
+        description=_(u'The mime-type identifies the type of data.'),
         default='',
+        missing_value='',
         required=False,
-        missing_value=''
         )
 
-    # TODO: remove the line below
-    #encoding = BytesLine(
     encoding = MimeDataEncoding(
         title = _(u'Encoding Type'),
         description=_(u'The encoding of the data if it is text.'),
         default='',
+        missing_value='',
         required=False,
-        missing_value=''
         )
 
-    # TODO: remove the line below
-    #data = Bytes(
     data = MimeData (
         title=_(u'Data'),
         description=_(u'The actual content of the file.'),
@@ -78,9 +107,6 @@
         required=False,
         )
 
-    def getSize():
-        """Return the byte-size of the data of the object."""
-
     def open(mode='r'):
         """Return a file like object for reading or updating.
         
@@ -88,27 +114,12 @@
         """
 
 
-class IReadFileAccess(Interface):
-    """File read interface."""
 
-    def read():
-        """Write access on file."""
-
-    def size():
-        """Size of the file."""
-
-
-class IWriteFileAccess(Interface):
-    """File write interface."""
-
-    def write():
-        """Write access on file."""
-
-
-
 class IFile(Interface):
     """File interface."""
 
+    # TODO: rember, we remove the contentType  field from the interface
+
     contents = Schema(IMime, "zope.app.file.Mime",
         title = _(u'The file data'),
         description = _(u'The mime information and file data, which can be '
@@ -128,20 +139,24 @@
         )
 
     # 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='',
+        missing_value='',
         required=False,
-        missing_value=''
         )
 
     def getSize():
         """Return the byte-size of the data of the object."""
 
+    def open(mode='r'):
+        """Return a file like object for reading or updating.
+        
+        Default is set to readonly, use mode='w' for write mode.
+        """
 
+
 class IFileContent(Interface):
     """Marker interface for content that can be managed as files.
 

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py	2005-01-23 01:59:08 UTC (rev 28916)
@@ -114,29 +114,16 @@
         return content
 
     def applyChanges(self, content):
-        print ""
-        print "applyChanges"
         field = self.context
-        print "field = self.context ", self.context
-        print "content ", content
 
         # create our new object value
         value = field.query(content, None)
-        print "value ", value
         if value is None:
             # TODO: ObjectCreatedEvent here would be nice
-            print "if value is None:"
             value = self.factory()
-            print "value ", value
 
         # apply sub changes, see if there *are* any changes
         # TODO: ObjectModifiedEvent here would be nice
-        print "self ", self
-        print "isProxy(self) ", isProxy(self)
-        print "isProxy(value) ", isProxy(value)
-        print "isProxy(field) ", isProxy(field)
-        print "isProxy(content) ", isProxy(content)
-        print "self.names ", self.names
         changes = applyWidgetsChanges(self, field.schema, target=value,
                                       names=self.names)
 

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py	2005-01-22 11:53:36 UTC (rev 28915)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/schema/_field.py	2005-01-23 01:59:08 UTC (rev 28916)
@@ -179,6 +179,7 @@
                                object.__class__.__name__))
         setattr(object, self.__name__, value)
 
+# TODO: remove this
 # we shouldn't do that in a field, we normaly don't know other fieldnames
 # like contentType. That's the part of the MimeType field/widget
 
@@ -238,60 +239,22 @@
 
 
 # TODO: add encodng vocabulary for selecting possible mime-types
-class MimeDataEncoding(Field):
+class MimeDataEncoding(BytesLine):
     """Field containing the encoding used for text-based files."""
     implements(IMimeDataEncoding, IFromUnicode)
 
     _type = str
 
-    def fromUnicode(self, u):
-        """
-        >>> b = Bytes(constraint=lambda v: 'x' in v)
 
-        >>> b.fromUnicode(u" foo x.y.z bat")
-        ' foo x.y.z bat'
-        >>> b.fromUnicode(u" foo y.z bat")
-        Traceback (most recent call last):
-        ...
-        ConstraintNotSatisfied:  foo y.z bat
-
-        """
-        v = str(u)
-        self.validate(v)
-        return v
-
-    def constraint(self, value):
-        return '\n' not in value
-
-
 # TODO: perhaps add mime-type vocabulary for possible mime-types.
 # If so, we need also to list the mime-types from the python lib mimetypes
-class MimeType(Field):
+class MimeType(BytesLine):
     """Field containing the mime-type for a file."""
     implements(IMimeType, IFromUnicode)
 
     _type = str
 
-    def fromUnicode(self, u):
-        """
-        >>> b = Bytes(constraint=lambda v: 'x' in v)
 
-        >>> b.fromUnicode(u" foo x.y.z bat")
-        ' foo x.y.z bat'
-        >>> b.fromUnicode(u" foo y.z bat")
-        Traceback (most recent call last):
-        ...
-        ConstraintNotSatisfied:  foo y.z bat
-
-        """
-        v = str(u)
-        self.validate(v)
-        return v
-
-    def constraint(self, value):
-        return '\n' not in value
-
-
 class Float(Orderable, Field):
     __doc__ = IFloat.__doc__
     implements(IFloat, IFromUnicode)



More information about the Zope3-Checkins mailing list