[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