[Checkins] SVN: zope.file/branches/0.2/ OK, here are 0.2 tests passing; again, this is the branch for 3.4 (IResult) but not blobs yet.

Gary Poster gary at zope.com
Wed Apr 18 00:18:48 EDT 2007


Log message for revision 74232:
  OK, here are 0.2 tests passing; again, this is the branch for 3.4 (IResult) but not blobs yet.

Changed:
  _U  zope.file/branches/0.2/
  A   zope.file/branches/0.2/bootstrap.py
  A   zope.file/branches/0.2/buildout.cfg
  U   zope.file/branches/0.2/setup.py
  U   zope.file/branches/0.2/src/zope/file/configure.zcml
  U   zope.file/branches/0.2/src/zope/file/download.py
  U   zope.file/branches/0.2/src/zope/file/download.txt

-=-

Property changes on: zope.file/branches/0.2
___________________________________________________________________
Name: svn:ignore
   + develop-eggs
bin
parts
.installed.cfg
dist


Copied: zope.file/branches/0.2/bootstrap.py (from rev 74228, zope.file/trunk/bootstrap.py)

Copied: zope.file/branches/0.2/buildout.cfg (from rev 74228, zope.file/trunk/buildout.cfg)

Modified: zope.file/branches/0.2/setup.py
===================================================================
--- zope.file/branches/0.2/setup.py	2007-04-18 04:16:42 UTC (rev 74231)
+++ zope.file/branches/0.2/setup.py	2007-04-18 04:18:46 UTC (rev 74232)
@@ -2,11 +2,27 @@
 
 setup(
     name="zope.file",
-    version="0.1dev",
+    version="0.2dev",
     packages=find_packages('src'),
     package_dir={'':'src'},
     namespace_packages=['zope'],
     include_package_data=True,
-    install_requirements = ['setuptools'],
-    zip_safe = False
+    zip_safe = False,
+    install_requires=['setuptools',
+                      'zope.app.appsetup',
+                      'zope.app.publication',
+                      'zope.app.wsgi',
+                      'zope.event',
+                      'zope.interface',
+                      'zope.publisher',
+                      'zope.security',
+                      'zope.mimetype',
+                      # "extras"
+                      'zope.app.testing',
+                      'zope.app.securitypolicy',
+                      'zope.app.zcmlfiles',
+                      'zope.testbrowser',
+                      'zope.formlib',
+                      'zope.app.server',
+                      ],
     )

Modified: zope.file/branches/0.2/src/zope/file/configure.zcml
===================================================================
--- zope.file/branches/0.2/src/zope/file/configure.zcml	2007-04-18 04:16:42 UTC (rev 74231)
+++ zope.file/branches/0.2/src/zope/file/configure.zcml	2007-04-18 04:18:46 UTC (rev 74232)
@@ -107,7 +107,7 @@
       />
 
   <class class=".download.DownloadResult">
-    <allow interface="zope.publisher.http.IResult"/>
+    <allow interface="zope.publisher.interfaces.http.IResult"/>
   </class>
 
 </configure>

Modified: zope.file/branches/0.2/src/zope/file/download.py
===================================================================
--- zope.file/branches/0.2/src/zope/file/download.py	2007-04-18 04:16:42 UTC (rev 74231)
+++ zope.file/branches/0.2/src/zope/file/download.py	2007-04-18 04:18:46 UTC (rev 74232)
@@ -20,50 +20,40 @@
 import zope.interface
 import zope.mimetype.interfaces
 import zope.publisher.browser
-import zope.publisher.http
+import zope.publisher.interfaces.http
 
 
 class Download(zope.publisher.browser.BrowserView):
 
     def __call__(self):
-        return DownloadResult(self.context, contentDisposition="attachment")
+        for k, v in getHeaders(self.context, contentDisposition="attachment"):
+            self.request.response.setHeader(k, v)
+        return DownloadResult(self.context)
 
 
 class Inline(zope.publisher.browser.BrowserView):
 
     def __call__(self):
-        return DownloadResult(self.context, contentDisposition="inline")
+        for k, v in getHeaders(self.context, contentDisposition="inline"):
+            self.request.response.setHeader(k, v)
+        return DownloadResult(self.context)
 
 
 class Display(zope.publisher.browser.BrowserView):
 
     def __call__(self):
+        for k, v in getHeaders(self.context):
+            self.request.response.setHeader(k, v)
         return DownloadResult(self.context)
 
-
-class DownloadResult(object):
-    """Result object for a download request."""
-
-    zope.interface.implements(
-        zope.publisher.http.IResult)
-
-    def getFile(self, context):
-        # This ensures that what's left has no connection to the
-        # application/database; ZODB BLOBs will provide a equivalent
-        # feature once available.
-        f = context.open('rb')
-        res = cStringIO.StringIO(f.read())
-        f.close()
-        return res
-
-    def __init__(self, context, contentType=None, downloadName=None,
-                 contentDisposition=None, contentLength=None):
+def getHeaders(context, contentType=None, downloadName=None,
+               contentDisposition=None, contentLength=None):
         if not contentType:
             cti = zope.mimetype.interfaces.IContentInfo(context, None)
             if cti is not None:
                 contentType = cti.contentType
         contentType = contentType or "application/octet-stream"
-        self.headers = ("Content-Type", contentType),
+        headers = ("Content-Type", contentType),
 
         downloadName = downloadName or context.__name__
         if contentDisposition:
@@ -71,14 +61,33 @@
                 contentDisposition += (
                     '; filename="%s"' % downloadName.encode("utf-8")
                     )
-            self.headers += ("Content-Disposition", contentDisposition),
+            headers += ("Content-Disposition", contentDisposition),
 
         if contentLength is None:
             contentLength = context.size
-        self.headers += ("Content-Length", str(contentLength)),
-        self.body = bodyIterator(self.getFile(context))
+        headers += ("Content-Length", str(contentLength)),
+        return headers
 
+def getFile(context):
+    # This ensures that what's left has no connection to the
+    # application/database; ZODB BLOBs will provide a equivalent
+    # feature once available.
+    f = context.open('rb')
+    res = cStringIO.StringIO(f.read())
+    f.close()
+    return res
 
+class DownloadResult(object):
+    """Result object for a download request."""
+
+    zope.interface.implements(zope.publisher.interfaces.http.IResult)
+
+    def __init__(self, context):
+        self._iter = bodyIterator(getFile(context))
+
+    def __iter__(self):
+        return self._iter
+
 CHUNK_SIZE = 64 * 1024
 
 

Modified: zope.file/branches/0.2/src/zope/file/download.txt
===================================================================
--- zope.file/branches/0.2/src/zope/file/download.txt	2007-04-18 04:16:42 UTC (rev 74231)
+++ zope.file/branches/0.2/src/zope/file/download.txt	2007-04-18 04:18:46 UTC (rev 74232)
@@ -9,8 +9,9 @@
 The download support is provided by two distinct objects:  A view that
 provides the download support using the information in the content
 object, and a result object that can be used to implement a file
-download by other views.  The result object can be used to override
-the content-type or the filename suggested to the browser.
+download by other views.  The view can override the content-type or the
+filename suggested to the browser using the standard IResponse.setHeader
+method.
 
 Note that result objects are intended to be used once and then
 discarded.
@@ -24,17 +25,18 @@
 Headers
 -------
 
-Now, let's create a download result for this file::
+Now, let's get the headers for this file.  We use a utility function called
+``getHeaders``::
 
-  >>> from zope.file.download import DownloadResult
-  >>> result = DownloadResult(f, contentDisposition='attachment')
+  >>> from zope.file.download import getHeaders
+  >>> headers = getHeaders(f, contentDisposition='attachment')
 
 Since there's no suggested download filename on the file, the
 Content-Disposition header doesn't specify one, but does indicate that
 the response body be treated as a file to save rather than to apply
 the default handler for the content type.
 
-  >>> sorted(result.headers)
+  >>> sorted(headers)
   [('Content-Disposition', 'attachment'),
    ('Content-Length', '0'),
    ('Content-Type', 'application/octet-stream')]
@@ -43,22 +45,21 @@
 Note that a default content type of 'application/octet-stream' is
 used.
 
-If the file object specifies a content type, that's used in the result
+If the file object specifies a content type, that's used in the headers
 by default::
 
   >>> f.mimeType = "text/plain"
-  >>> result = DownloadResult(f, contentDisposition='attachment')
-  >>> sorted(result.headers)
+  >>> headers = getHeaders(f, contentDisposition='attachment')
+  >>> sorted(headers)
   [('Content-Disposition', 'attachment'),
    ('Content-Length', '0'),
    ('Content-Type', 'text/plain')]
 
-Alternatively, a content type can be specified to the result
-constructor::
+Alternatively, a content type can be specified to ``getHeaders``::
 
-  >>> result = DownloadResult(f, contentType="text/xml",
-  ...                         contentDisposition='attachment')
-  >>> sorted(result.headers)
+  >>> headers = getHeaders(f, contentType="text/xml",
+  ...                      contentDisposition='attachment')
+  >>> sorted(headers)
   [('Content-Disposition', 'attachment'),
    ('Content-Length', '0'),
    ('Content-Type', 'text/xml')]
@@ -66,27 +67,27 @@
 The filename provided to the browser can be controlled similarly.  If
 the content object provides one, it will be used by default::
 
-  >>> result = DownloadResult(f, contentDisposition='attachment')
-  >>> sorted(result.headers)
+  >>> headers = getHeaders(f, contentDisposition='attachment')
+  >>> sorted(headers)
   [('Content-Disposition', 'attachment'),
    ('Content-Length', '0'),
    ('Content-Type', 'text/plain')]
 
-Providing an alternate name to the result constructor overrides the
-download name from the file::
+Providing an alternate name to ``getHeaders`` overrides the download
+name from the file::
 
-  >>> result = DownloadResult(f, downloadName="foo.txt",
-  ...                         contentDisposition='attachment')
-  >>> sorted(result.headers)
+  >>> headers = getHeaders(f, downloadName="foo.txt",
+  ...                      contentDisposition='attachment')
+  >>> sorted(headers)
   [('Content-Disposition', 'attachment; filename="foo.txt"'),
    ('Content-Length', '0'),
    ('Content-Type', 'text/plain')]
 
 The default Content-Disposition header can be overridden by providing
-an argument to the DownloadResult constructor::
+an argument to ``getHeaders``::
 
-  >>> result = DownloadResult(f, contentDisposition="inline")
-  >>> sorted(result.headers)
+  >>> headers = getHeaders(f, contentDisposition="inline")
+  >>> sorted(headers)
   [('Content-Disposition', 'inline'),
    ('Content-Length', '0'),
    ('Content-Type', 'text/plain')]
@@ -94,8 +95,8 @@
 If the `contentDisposition` argument is not provided, none will be
 included in the headers::
 
-  >>> result = DownloadResult(f)
-  >>> sorted(result.headers)
+  >>> headers = getHeaders(f)
+  >>> sorted(headers)
   [('Content-Length', '0'),
    ('Content-Type', 'text/plain')]
 
@@ -103,9 +104,12 @@
 Body
 ----
 
-Since there's no data in this file, there are no body chunks:
+We use DownloadResult to deliver the content to the browser.  Since
+there's no data in this file, there are no body chunks:
 
-  >>> list(result.body)
+  >>> from zope.file.download import DownloadResult
+  >>> result = DownloadResult(f)
+  >>> list(result)
   []
 
 We still need to see how non-empty files are handled.  Let's write
@@ -119,7 +123,7 @@
 expect::
 
   >>> result = DownloadResult(f)
-  >>> L = list(result.body)
+  >>> L = list(result)
   >>> "".join(L)
   'some text'
 
@@ -130,23 +134,23 @@
   >>> w.flush()
 
   >>> result = DownloadResult(f)
-  >>> L = list(result.body)
+  >>> L = list(result)
   >>> len(L) > 1
   True
 
 Once iteration over the body has completed, further iteration will not
 yield additional data::
 
-  >>> list(result.body)
+  >>> list(result)
   []
 
 
 The Download View
 -----------------
 
-Now that we've seen the result object, let's take a look at the basic
-download view that uses it.  We'll need to add a file object where we
-can get to it using a browser::
+Now that we've seen the ``getHeaders`` function and the result object,
+let's take a look at the basic download view that uses them.  We'll need
+to add a file object where we can get to it using a browser::
 
   >>> f = File()
   >>> f.mimeType = "text/plain"



More information about the Checkins mailing list