[Zope-Checkins] CVS: Zope3/lib/python/Zope/Publisher - IApplicationRequest.py:1.1.2.1 IPublicationRequest.py:1.1.2.1 IPublisher.py:1.1.2.1 IPublisherRequest.py:1.1.2.1 IPublisherResponse.py:1.1.2.1 BaseRequest.py:1.1.2.23.4.1 BaseResponse.py:1.1.2.8.6.1 DefaultPublication.py:1.1.2.9.4.1 Exceptions.py:1.1.2.10.6.1 IPublication.py:1.1.2.9.6.1 Publish.py:1.1.2.13.6.1

Jim Fulton jim@zope.com
Sat, 16 Mar 2002 09:44:31 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/Publisher
In directory cvs.zope.org:/tmp/cvs-serv24057/python/Zope/Publisher

Modified Files:
      Tag: Zope3-publisher-refactor-branch
	BaseRequest.py BaseResponse.py DefaultPublication.py 
	Exceptions.py IPublication.py Publish.py 
Added Files:
      Tag: Zope-3x-branch
	IApplicationRequest.py IPublicationRequest.py IPublisher.py 
	IPublisherRequest.py IPublisherResponse.py 
Log Message:
Checking in partial publisher refactoring on the
Zope3-publisher-refactor-branch branch to facilitate collaboration
with Stephan.



=== Added File Zope3/lib/python/Zope/Publisher/IApplicationRequest.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

Revision information:
$Id: IApplicationRequest.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""

from Interface import Interface
from Interface.Common.Mapping import IEnumerableMapping

class IApplicationRequest(IEnumerableMapping):
    """Features that support application logic
    """

    def getBody():
        """Return the body of the request as a string
        """
        

    def getBodyFile():
        """Return the body of the request as a file
        """


=== Added File Zope3/lib/python/Zope/Publisher/IPublicationRequest.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

Revision information:
$Id: IPublicationRequest.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""

from Interface import Interface

class IPublicationRequest(Interface):
    """Interface provided by requests to IPublication objects
    """

    def hold(object):
        """Hold a reference to an object until the request is closed
        """

    def getTraversalStack():
        """Return the request traversal stack

        This is a sequence of steps to traverse in reverse order. They
        will be traversed from last to first.
        """

    def setTraversalStack(stack):
        """Change the traversal stack.

        See getTraversalStack.
        """
        
    def getPositionalArguments():
        """Return the positional arguments given to the request.
        """
        


=== Added File Zope3/lib/python/Zope/Publisher/IPublisher.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

Revision information:
$Id: IPublisher.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""

from Interface import Interface

def IPublisher(Interface):

    def publish(request):
        """Publish a request

        The request must be an IPublisherRequest.
        """

    


=== Added File Zope3/lib/python/Zope/Publisher/IPublisherRequest.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

Revision information:
$Id: IPublisherRequest.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""

from Interface import Interface

class IPublisherRequest(Interface):
    """Interface used by the publsher
    """

    def supports_retry():
        """Check whether the request supports retry

        Return a boolean value indicating whether the request can be retried.
        """

    def retry():
        """Return a retry request

        Return a request suitable for repeating the publication attempt.
        """

    def getPublication():
        """Return the requets's publication object

        The publication object, an IRequestPublication provides
        application-specific functionality hooks.
        """

    def getResponse():
        """Return the request's response object

        Return an IPublisherResponse for the request.
        """
    
    def traverse(publication, object):
        """Traverse from the given object to the published object

        The published object is returned.

        The following hook methods on the publication will be called:

          - callTraversalHooks is called before each step and after
            the last step.

          - traverseName to actually do a single traversal
          
        """

    def close():
        """Release resources held by the request.
        """

    def processInputs():
        """Do any input processing that needs to bve done before traversing

        This is done after construction to allow the publisher to
        handle errors that arise.
        """


=== Added File Zope3/lib/python/Zope/Publisher/IPublisherResponse.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

Revision information:
$Id: IPublisherResponse.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""

from Interface import Interface

class IPublisherResponse:
    """Interface used by the publsher
    """

    def setBody(result):
        """Set's the response result value.
        """

    def handleException(exc_info):
        """Handle an otherwise unhandled exception.
        """

    def outputBody():
        """Output the response to the client
        """
        


=== Zope3/lib/python/Zope/Publisher/BaseRequest.py 1.1.2.23 => 1.1.2.23.4.1 ===
 from types import StringType
 from Zope.ComponentArchitecture.IViewService import IViewRequest
-
-def pc_quote(s):
-    """
-    URL-quotes only the characters not allowed in the path
-    component of a URL.  See RFC 2396 Section 3.3.
+from BaseResponse import BaseResponse
+from IApplicationRequest import IApplicationRequest
+from IPublisherRequest import IPublisherRequest
+from IPublicationRequest import IPublicationRequest
+
+class IRequest(IPublisherRequest, IPublicationRequest, IApplicationRequest,
+               IViewRequest):
+    """The basic request contract
     """
-    return quote(s, "/:@&=+$,;")
-
 
-_marker = []
+_marker = object()
 
 class BaseRequest:
     """Represents a publishing request.
@@ -42,76 +43,48 @@
     collection of variable to value mappings.
     """
 
-    __implements__ = IViewRequest
-    # XXX: maybe more to implement :-)
-
-    body_instream = None  # The body input stream
-    common = {}           # Data common to all requests
-    args = ()             # Positional arguments
-    publication = None    # Publication object
-
-    # _held contains objects kept until the request is closed,
-    # such as the database connection closer.
-    _held = ()
-    _request_default = None  # Overrides publication.getDefaultTraversal().
-    _body = None             # Cached body
-
-    # URL is a string built up during traversal and url_quoted.
-    # It does not include names built from publication.getDefaultTraversal().
-    URL = ''
-    effective_url = '' # URL plus the names built from getDefaultTraversal().
-    to_traverse = ()   # A sequence containing the names to traverse, reversed.
-    steps = ()         # A sequence of names built up during traversal.
-    quoted_steps = ()  # Same as steps but quoted.
-    
-    # traversed contains the objects traversed in order.
-    traversed = ()
-
-    def __init__(self, response, body_instream=None, publication=None):
-        """
-        The constructor should not raise errors.
-        processInputs() should perform the unsafe initialization.
-        """
-        self.response = response
-        self.body_instream = body_instream
-        self.publication = publication
-        self.steps = []
-        self.quoted_steps = []
-        self.other = {}
+    __implements__ = IRequest
+    _common = {} # Variables common to all request instances
+    _held = ()   # Objects held until the request is closed
+
+    def __init__(self, body_instream, outstream, environ):
+        self.__traversal_stack = ()
+        self._other = {} 
+        self._environ = environ
+        self.__args = ()
+        self.__response = self._createResponse(outstream)
+        self.__body_instream = body_instream
+        self.__held = ()
+
+    def getPositionalArguments(self):
+        return self.__args
+
+    def _createResponse(self, outstream):
+        # Should be overridden by subclasses
+        return BaseResponse(outstream)
 
     def close(self):
-        self.other.clear()
-        self._held = None  # Release held objects
-        self.traversed = None
+        self._other.clear()
+        self.__held = ()  # Release held objects
+        self.__traversed = None
 
     def processInputs(self):
         """Do any initialization that could raise errors
         """
 
     def getPublication(self):
-        return self.publication
-
-    def __len__(self):
-        return 1
+        return self.__publication
 
-    _key_handlers = {}
-
-    def getRequest(self, default=None):
-        return self
-    _key_handlers['REQUEST'] = getRequest
+    def setPublication(self, pub):
+        self.__publication = pub
 
     def getResponse(self, default=None):
-        return self.response
-    _key_handlers['RESPONSE'] = getResponse
-
-    def getURL(self, default=None):
-        return self.URL
-    _key_handlers['URL'] = getURL
+        return self.__response
 
     def getBody(self, default=None):
-        body = self._body
+        body = self.__body
         if body is None:
-            s = self.body_instream
+            s = self.__body_instream
             if s is None:
                 return default
             p = s.tell()
@@ -120,52 +93,38 @@
             s.seek(p)
             self._body = body
         return body
-    _key_handlers['BODY'] = getBody
 
     def getBodyFile(self, default=None):
         return self.body_instream
-    _key_handlers['BODYFILE'] = getBodyFile
-
-    def getEffectiveURL(self):
-        return self.effective_url or self.URL
 
     def get(self, key, default=None):
-        """Get a variable value
 
-        Return a value for the required variable name.
-        The value will be looked up from one of the request data
-        categories. The search order is environment variables,
-        other variables, form data, and then cookies. 
-        
-        """
-        handler = self._key_handlers.get(key, None)
-        if handler is not None:
-            v = handler(self, _marker)
-            if v is not _marker:
-                return v
-        v = self.other.get(key, _marker)
-        if v is not _marker:
-            return v
-        v = self.common.get(key, _marker)
-        if v is not _marker:
-            return v
+        result = self._other.get(key, self)
+        if result is not self: return result 
+
+        result = self._common.get(key, self)
+        if result is not self: return result 
+
+        result = self._environ.get(key, self)
+        if result is not self: return result
 
         return default
 
     def __getitem__(self, key):
-        res = self.get(key, _marker)
-        if res is _marker:
+        result = self.get(key, _marker)
+        if result is _marker:
             raise KeyError, key
         else:
-            return res
+            return result
 
     def has_key(self, key):
         return self.get(key, _marker) is not _marker
 
     def keys(self):
-        keys = {'URL':1}
-        keys.update(self.common)
-        keys.update(self.other)
+        keys = {}
+        keys.update(self._common)
+        keys.update(self._other)
+        keys.update(self._environ)
         return keys.keys()
 
     def items(self):
@@ -192,34 +151,18 @@
         return '<%s instance at 0x%x, URL=%s>' % (
             str(self.__class__), id(self), `self.URL`)
 
-    def splitPath(self, path):
-        # Split and clean up the path.
-        if path.startswith('/'):  path = path[1:]
-        if path.endswith('/'): path = path[:-1]
-        clean = []
-        for item in path.split('/'):
-            if not item or item == '.':
-                continue
-            elif item == '..':
-                del clean[-1]
-            else: clean.append(item)
-        return clean
-
-    def setRequestDefault(self, path):
+    def setPathSuffix(self, steps):
         """
         Adds the specified steps to the URL, overriding
         publication.getDefaultTraversal().
         """
-        steps = path.split('/')
-        steps.reverse()
-        self._request_default = steps
+        self.__path_suffix = steps
 
-    def changeTraversalStack(self, names):
-        """
-        Private.  Can be called by a traversal hook.
-        'names' argument should be in reverse.
-        """
-        self.to_traverse = names
+    def getTraversalStack(self):
+        return self.__traversal_stack
+
+    def setTraversalStack(self, stack):
+        self.__traversal_stack = stack
 
 
     def traverse(self, publication, object):
@@ -227,88 +170,32 @@
         Traverses to an object and returns it.
         Private.
         """
-
-        # XXX This is too Browser specific. I mean, setBase?
-        
-        traversal_altered = 0 # flag for adding traversal steps
-        add_steps = None
-        path_str = self.get('PATH_INFO', '').strip()
-        to_traverse = self.splitPath(path_str)
-
-        self.traversed = traversed = []
-        traversed.append(object)
-        steps = self.steps
-        self.quoted_steps = quoted_steps = map(pc_quote, steps)
-        to_traverse.reverse()
-        self.to_traverse = to_traverse
-
+        to_traverse = self.__traversal_stack
         prev_object = None
         while 1:
             if object is not prev_object:
                 # Invoke hooks (but not more than once).
                 publication.callTraversalHooks(self, object)
                 # A hook may have called changeTraversalStack().
-                to_traverse = self.to_traverse
+                to_traverse = self.__traversal_stack
             prev_object = object
 
             if to_traverse:
                 # Traverse to the next step.
                 entry_name = to_traverse.pop()
-                if entry_name:
-                    qstep = pc_quote(entry_name)
-                    quoted_steps.append(qstep)
-                        
-                    if traversal_altered:
-                        # The effective URL includes the altered traversal.
-                        #import pdb; pdb.set_trace()
-                        e_url = self.effective_url or self.URL
-                        self.effective_url = '%s/%s' % (e_url, qstep)
-                    else:
-                        # Build up the URL to the object, but not
-                        # to the default traversal.                        
-                        self.URL = '%s/%s' % (self.URL, qstep)
-                    subobject = publication.traverseName(
-                        self, object, entry_name)
-                    object = subobject
-                    traversed.append(object)
-                    steps.append(entry_name)
-
+                subobject = publication.traverseName(
+                    self, object, entry_name)
+                object = subobject
             else:
-                add_steps = self._request_default
-                
-                if add_steps:
-                    self._request_default = None
-                
-                if add_steps is None:
-                    object, add_steps = publication.getDefaultTraversal(
-                        self, object)
-                    
-                if add_steps:
-                    traversal_altered = 1
-                    to_traverse.extend(add_steps)
-                else:
-                    # Finished traversal.
-                    break
-        
-        if traversal_altered:
-            eurl = self.effective_url
-            l = eurl.rfind('/')
-            if l >= 0: eurl = eurl[:l+1] # XXX Quick bug fix, need better impl
-            self.response.setBase(eurl)
-
-        self.traversed = tuple(traversed)  # No more changes allowed
-        parents = traversed[:]
-        parents.pop()
-        parents.reverse()
-        self.other['PARENTS'] = tuple(parents)
-        self.other['PUBLISHED'] = object
+                # Finished traversal.
+                break
 
         return object
 
     def supports_retry(self):
         return 0
 
-    def _hold(self, object):
+    def hold(self, object):
         """
         Holds a reference to an object to delay its destruction until mine
         """


=== Zope3/lib/python/Zope/Publisher/BaseResponse.py 1.1.2.8 => 1.1.2.8.6.1 ===
 from Exceptions import Unauthorized
 
-class BaseResponse:
+class BaseResponse(object):
     """Base Response Class
 
     What should be here?
@@ -28,8 +28,6 @@
     
     def __init__(self, outstream):
         self.outstream = outstream
-        self.headers = {}
-        self.cookies = {}
 
     def setDebugMode(self, d):
         self.debug_mode = d


=== Zope3/lib/python/Zope/Publisher/DefaultPublication.py 1.1.2.9 => 1.1.2.9.4.1 ===
 
     def beforeTraversal(self, request):
-        pass
+        # Lop off leading and trailing empty names
+        stack = request.getTraversalStack()
+        while stack and not stack[-1]:
+            stack.pop() # toss a trailing empty name
+        while stack and not stack[0]:
+            stack.pop(0) # toss a leading empty name
+        request.setTraversalStack(stack)
 
     def getApplication(self, request):
         return self.app
@@ -41,8 +47,7 @@
                     TypeError, AttributeError):
                 raise NotFound(ob, name, request)
         if self.require_docstrings and not getattr(subob, '__doc__', None):
-            raise DebugError(subob, 'Missing or empty doc string at: %s' %
-                             request.getURL())
+            raise DebugError(subob, 'Missing or empty doc string')
         return subob
 
     def getDefaultTraversal(self, request, ob):
@@ -52,13 +57,13 @@
         pass
 
     def callObject(self, request, ob):
-        return mapply(ob, request.args, request)
+        return mapply(ob, request.getPositionalArguments(), request)
 
     def afterCall(self, request):
         pass
 
     def handleException(self, request, exc_info, retry_allowed=1):
         # Let the response handle it as best it can.
-        response = request.response
+        response = request.getResponse()
         response.handleException(exc_info)
 


=== Zope3/lib/python/Zope/Publisher/Exceptions.py 1.1.2.10 => 1.1.2.10.6.1 ===
         self.ob = ob
         self.name = name
-        if request is not None:
-            url = request.getEffectiveURL()
-        else:
-            url = None
-        self.url = url
 
     def getObject(self):
         return self.ob
@@ -41,10 +36,7 @@
         return self.name
 
     def __str__(self):
-        if self.url:
-            return self.url
-        else:
-            return 'Object: %s, name: %s' % (`self.ob`, `self.name`)
+        return 'Object: %s, name: %s' % (`self.ob`, `self.name`)
 
 
 class DebugError (TraversalException):


=== Zope3/lib/python/Zope/Publisher/IPublication.py 1.1.2.9 => 1.1.2.9.6.1 ===
         """
         Either:
-        - sets the body of request.response,
+        - sets the body of request.getResponse(),
         - raises a Retry exception, or
         - throws another exception, which is a Bad Thing.
-        Returns the response object.
         """
 


=== Zope3/lib/python/Zope/Publisher/Publish.py 1.1.2.13 => 1.1.2.13.6.1 ===
     """
     publication = None
-    response = request.response
+    response = request.getResponse()
 
     try:
         request.processInputs()
@@ -49,7 +49,7 @@
     if publication is not None:
         publication.handleException(request, exc_info, allow_retry)
     else:
-        request.response.handleException(exc_info)
+        request.getResponse().handleException(exc_info)
 
 
 
@@ -78,14 +78,12 @@
                 # Bad exception handler or retry method.
                 # Re-raise after outputting the response.
                 to_raise = sys.exc_info()
-                request.response.setStatus(500) # Try to indicate an error.
                 break
 
-        response = request.response
+        response = request.getResponse()
         response.outputBody()
         if to_raise is not None:
             raise to_raise[0], to_raise[1], to_raise[2]
-        return response.getStatus()
 
     finally:
         to_raise = None  # Avoid circ. ref.