[Zope3-checkins] SVN: Zope3/trunk/ Merged from 3.2 branch:
Jim Fulton
jim at zope.com
Sat Dec 24 11:55:56 EST 2005
Log message for revision 41032:
Merged from 3.2 branch:
------------------------------------------------------------------------
r41002 | jim | 2005-12-23 14:51:22 -0500 (Fri, 23 Dec 2005) | 13 lines
Added new machinery that allows published methods to just return
files.
Also:
- IResult is now a private interface
- When we look up an IResult, we use a multi-adapter call with the
request. This means that result adapters have access to the
request and response, which would allow a significant
simplification of the result API. This is why we made it
private now, so we can change it later.
Changed:
U Zope3/trunk/doc/CHANGES.txt
U Zope3/trunk/src/zope/app/configure.zcml
U Zope3/trunk/src/zope/app/publisher/http.zcml
A Zope3/trunk/src/zope/app/wsgi/configure.zcml
A Zope3/trunk/src/zope/app/wsgi/fileresult.py
A Zope3/trunk/src/zope/app/wsgi/fileresult.txt
U Zope3/trunk/src/zope/app/wsgi/tests.py
U Zope3/trunk/src/zope/publisher/http.py
U Zope3/trunk/src/zope/publisher/httpresults.txt
U Zope3/trunk/src/zope/publisher/interfaces/http.py
-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/doc/CHANGES.txt 2005-12-24 16:55:56 UTC (rev 41032)
@@ -123,32 +123,19 @@
- addMenuItem directive supports a `layer` attribute.
- - Added a new API, zope.publisher.interfaces.http.IResult. See
- the file httpresults.txt in the zope.publisher package for
- details.
+ - Changed the Publisher Response API.
- - Formalized the Publisher Response API.
+ + Large results can now ne handled efeciently by returning
+ files rather than strings. See the file httpresults.txt in
+ the zope.publisher package.
- + Until now the publisher made assumptions about the form of ouput of
- a publishing process. Either the called method returned a string
- (regular or unicode) or the response's write() method was used
- directly to write the data. Those models do not work well with some
- protocols. Thus, now the publisher deals with result objects. Those
- are generally not well defined, but for HTTP they must implement the
- IResult interface.
+ + The unused response.write method is no-longer supported.
+ HTTP responses provide two new methods that make reading the output
easier: `consumeBody()` and `consumeBodyIter()`. Either method can
be only called once. After that the output iterator is used and
empty.
- + The WSGI specification specifically has some provisions in it that
- supported our use of writing directly to the output stream. However,
- this method of providing an output is strongly discouraged. Instead,
- the application should return an iterable. Using the new IResult
- implementation in the HTTP publisher, we can now return such an
- iterable.
-
+ When a retry is issued in the publisher, then a new request is
created. This means that the request (including its response) that
were passed into `publish()` are not necessarily the same that are
Modified: Zope3/trunk/src/zope/app/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/configure.zcml 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/src/zope/app/configure.zcml 2005-12-24 16:55:56 UTC (rev 41032)
@@ -71,6 +71,7 @@
<include package="zope.app.applicationcontrol" />
<include package="zope.app.dublincore" />
<include package="zope.app.introspector" />
+ <include package="zope.app.wsgi" />
<!-- Content types -->
Modified: Zope3/trunk/src/zope/app/publisher/http.zcml
===================================================================
--- Zope3/trunk/src/zope/app/publisher/http.zcml 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/src/zope/app/publisher/http.zcml 2005-12-24 16:55:56 UTC (rev 41032)
@@ -15,7 +15,7 @@
</content>
<class class="zope.publisher.http.DirectResult">
- <allow interface="zope.publisher.interfaces.http.IResult" />
+ <allow interface="zope.publisher.http.IResult" />
</class>
</configure>
Copied: Zope3/trunk/src/zope/app/wsgi/configure.zcml (from rev 41002, Zope3/branches/3.2/src/zope/app/wsgi/configure.zcml)
Copied: Zope3/trunk/src/zope/app/wsgi/fileresult.py (from rev 41002, Zope3/branches/3.2/src/zope/app/wsgi/fileresult.py)
Copied: Zope3/trunk/src/zope/app/wsgi/fileresult.txt (from rev 41002, Zope3/branches/3.2/src/zope/app/wsgi/fileresult.txt)
Modified: Zope3/trunk/src/zope/app/wsgi/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/tests.py 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/src/zope/app/wsgi/tests.py 2005-12-24 16:55:56 UTC (rev 41032)
@@ -15,8 +15,14 @@
$Id$
"""
+import tempfile
import unittest
+
+from zope import component, interface
from zope.testing import doctest
+
+import zope.app.testing.functional
+import zope.publisher.interfaces.browser
from zope.app.testing import placelesssetup
from zope.app.publication.requestpublicationregistry import factoryRegistry
from zope.app.publication.requestpublicationfactories import BrowserFactory
@@ -25,10 +31,78 @@
placelesssetup.setUp(test)
factoryRegistry.register('GET', '*', 'browser', 0, BrowserFactory())
+
+
+class FileView:
+
+ interface.implements(zope.publisher.interfaces.browser.IBrowserPublisher)
+ component.adapts(interface.Interface,
+ zope.publisher.interfaces.browser.IBrowserRequest)
+
+ def __init__(self, _, request):
+ self.request = request
+
+ def browserDefault(self, *_):
+ return self, ()
+
+ def __call__(self):
+ self.request.response.setHeader('content-type', 'text/plain')
+ f = tempfile.TemporaryFile()
+ f.write("Hello\nWorld!\n")
+ return f
+
+
+def test_file_returns():
+ """We want to make sure that file returns work
+
+Let's register a view that returns a temporary file and make sure that
+nothing bad happens. :)
+
+ >>> component.provideAdapter(FileView, name='test-file-view.html')
+ >>> from zope.security import checker
+ >>> checker.defineChecker(
+ ... FileView,
+ ... checker.NamesChecker(['browserDefault', '__call__']),
+ ... )
+
+ >>> from zope.testbrowser import Browser
+ >>> browser = Browser()
+ >>> browser.handleErrors = False
+ >>> browser.open('http://localhost/@@test-file-view.html')
+ >>> browser.headers['content-type']
+ 'text/plain'
+
+ >>> browser.headers['content-length']
+ '13'
+
+ >>> print browser.contents
+ Hello
+ World!
+ <BLANKLINE>
+
+Clean up:
+
+ >>> checker.undefineChecker(FileView)
+ >>> component.provideAdapter(
+ ... None,
+ ... (interface.Interface,
+ ... zope.publisher.interfaces.browser.IBrowserRequest),
+ ... zope.publisher.interfaces.browser.IBrowserPublisher,
+ ... 'test-file-view.html',
+ ... )
+
+
+"""
+
def test_suite():
+
+ functional_suite = doctest.DocTestSuite()
+ functional_suite.layer = zope.app.testing.functional.Functional
+
return unittest.TestSuite((
+ functional_suite,
doctest.DocFileSuite(
- 'README.txt',
+ 'README.txt', 'fileresult.txt',
setUp=setUp,
tearDown=placelesssetup.tearDown,
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
Modified: Zope3/trunk/src/zope/publisher/http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/http.py 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/src/zope/publisher/http.py 2005-12-24 16:55:56 UTC (rev 41032)
@@ -24,8 +24,9 @@
import logging
from tempfile import TemporaryFile
+from zope import component, interface
+
from zope.deprecation import deprecation
-from zope.interface import implements
from zope.publisher import contenttype
from zope.publisher.interfaces.http import IHTTPCredentials
@@ -34,7 +35,7 @@
from zope.publisher.interfaces.http import IHTTPPublisher
from zope.publisher.interfaces import Redirect
-from zope.publisher.interfaces.http import IHTTPResponse, IResult
+from zope.publisher.interfaces.http import IHTTPResponse
from zope.publisher.interfaces.http import IHTTPApplicationResponse
from zope.publisher.interfaces.logginginfo import ILoggingInfo
from zope.i18n.interfaces import IUserPreferredCharsets
@@ -251,7 +252,9 @@
values will be looked up in the order: environment variables,
other variables, form data, and then cookies.
"""
- implements(IHTTPCredentials, IHTTPRequest, IHTTPApplicationRequest)
+ interface.implements(IHTTPCredentials,
+ IHTTPRequest,
+ IHTTPApplicationRequest)
__slots__ = (
'__provides__', # Allow request to directly provide interfaces
@@ -593,8 +596,32 @@
d.update(self._cookies)
return d.keys()
+
+class IResult(interface.Interface):
+ """HTTP result.
+
+ WARNING! This is a PRIVATE interface and VERY LIKELY TO CHANGE!
+
+ The result provides the result in a form suitable for delivery to HTTP
+ clients.
+
+ IMPORTANT: The result object may be held indefinitely by a server and may
+ be accessed by arbitrary threads. For that reason the result should not
+ hold on to any application resources and should be prepared to be invoked
+ from any thread.
+ """
+
+ headers = interface.Attribute(
+ 'A sequence of tuples of result headers, such as '
+ '"Content-Type" and "Content-Length", etc.')
+
+ body = interface.Attribute(
+ 'An iterable that provides the body data of the response.')
+
+
+
class HTTPResponse(BaseResponse):
- implements(IHTTPResponse, IHTTPApplicationResponse)
+ interface.implements(IHTTPResponse, IHTTPApplicationResponse)
__slots__ = (
'authUser', # Authenticated user string
@@ -771,16 +798,21 @@
def setResult(self, result):
- r = IResult(result, None)
- if r is None:
- if isinstance(result, basestring):
- body, headers = self._implicitResult(result)
- r = DirectResult((body,), headers)
- elif result is None:
- body, headers = self._implicitResult('')
- r = DirectResult((body,), headers)
- else:
- raise TypeError('The result should be adaptable to IResult.')
+ if IResult.providedBy(result):
+ r = result
+ else:
+ r = component.queryMultiAdapter((result, self._request), IResult)
+ if r is None:
+ if isinstance(result, basestring):
+ body, headers = self._implicitResult(result)
+ r = DirectResult((body,), headers)
+ elif result is None:
+ body, headers = self._implicitResult('')
+ r = DirectResult((body,), headers)
+ else:
+ raise TypeError(
+ 'The result should be adaptable to IResult.')
+
self._result = r
self._headers.update(dict([(k, [v]) for (k, v) in r.headers]))
if not self._status_set:
@@ -937,7 +969,7 @@
class HTTPCharsets(object):
- implements(IUserPreferredCharsets)
+ interface.implements(IUserPreferredCharsets)
def __init__(self, request):
self.request = request
@@ -1009,7 +1041,7 @@
application to specify all headers related to the content, such as the
content type and length.
"""
- implements(IResult)
+ interface.implements(IResult)
def __init__(self, body, headers=()):
self.body = body
@@ -1023,3 +1055,4 @@
including content type and length.
"""
return DirectResult((body,), headers)
+
Modified: Zope3/trunk/src/zope/publisher/httpresults.txt
===================================================================
--- Zope3/trunk/src/zope/publisher/httpresults.txt 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/src/zope/publisher/httpresults.txt 2005-12-24 16:55:56 UTC (rev 41032)
@@ -30,67 +30,15 @@
Returning large amounts of data without storing the data in memory
------------------------------------------------------------------
-Starting in Zope 3.2, a published object (e.g. a view or view method)
-can return any object as long as it is is adaptable to
-zope.publisher.interfaces.http.IResult::
+To return a large result, you should write the result to a temporary
+file (tempfile.TemporaryFile) and return the temporary file.
+Alternatively, if the data you want to return is already in a
+(non-temporary) file, just open and return that file. The publisher
+(actually an adapter used by the publisher) will handle a returned
+file very efficiently.
- class IResult(Interface):
- """HTTP result.
+The publisher will compute the response content length from the file
+automatically. It is up to applications to set the content type.
+It will also take care of positioning the file to it's beginning,
+so applications don't need to do this beforehand.
- The result provides the result in a form suitable for delivery to HTTP
- clients.
-
- IMPORTANT: The result object may be held indefinitely by a server and may
- be accessed by arbitrary threads. For that reason the result should not
- hold on to any application resources and should be prepared to be invoked
- from any thread.
- """
-
- headers = Attribute('A sequence of tuples of result headers, such as'
- '"Content-Type" and "Content-Length", etc.')
-
- body = Attribute('An iterable that provides the body data of the'
- 'response.')
-
-The result object has headers and an iterable body. The ability to
-supply headers in a result is useful for adapters that compute headers
-by inspecting a the object being adapted.
-
-There is a helper class, zope.publisher.http.DirectResult that can be
-used to compute result objects.
-
-When an published object returns a string. the string is inspected to
-determine response headers (like content type and content length) and
-a result is created using DirectResult.
-
-If you want to return a large amont of data, you can create a result
-object yourself. A good way to do this is to copy the data to a
-temporary file and return an iterator to that::
-
- import tempfile
- file = tempfile.TemporaryFile()
-
- # ... write data to the file ...
-
- def fileiterator(file, bufsize=8192):
- while 1:
- data = file.read(bufsize)
- if data:
- yield data
- else:
- break
-
- file.close()
-
- return DirectResult(fileiterator(file),
- [('Content-Length', mydatalength),
- ('Content-Type', mydatatype),
- ])
-
-We should provide some helper objects that automate more of this, and
-we probably will in later revisions.
-
-IMPORTANT NOTE: the iterator that you pass to DirectResult must *not*
-use any application resources. When the iterator is called,
-application resoures may have been released or be in use by another
-thread.
Modified: Zope3/trunk/src/zope/publisher/interfaces/http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/interfaces/http.py 2005-12-24 16:54:02 UTC (rev 41031)
+++ Zope3/trunk/src/zope/publisher/interfaces/http.py 2005-12-24 16:55:56 UTC (rev 41032)
@@ -373,7 +373,7 @@
"""
def setResult(result):
- """Sets the response result value that is adaptable to ``IResult``.
+ """Sets the response result value to a string or a file.
"""
def consumeBody():
@@ -389,23 +389,3 @@
Note that this function can be only requested once, since it is
constructed from the result.
"""
-
-
-class IResult(Interface):
- """HTTP result.
-
- The result provides the result in a form suitable for delivery to HTTP
- clients.
-
- IMPORTANT: The result object may be held indefinitely by a server and may
- be accessed by arbitrary threads. For that reason the result should not
- hold on to any application resources and should be prepared to be invoked
- from any thread.
- """
-
- headers = Attribute('A sequence of tuples of result headers, such as'
- '"Content-Type" and "Content-Length", etc.')
-
- body = Attribute('An iterable that provides the body data of the'
- 'response.')
-
More information about the Zope3-Checkins
mailing list