[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