[Zope-Checkins] CVS: Zope3/lib/python/Zope/Publisher - BaseRequest.py:1.1.2.1 BaseResponse.py:1.1.2.1 Exceptions.py:1.1.2.1 IPublication.py:1.1.2.1 Publish.py:1.1.2.1 __init__.py:1.1.2.1

Shane Hathaway shane@digicool.com
Wed, 14 Nov 2001 16:44:22 -0500


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

Added Files:
      Tag: Zope-3x-branch
	BaseRequest.py BaseResponse.py Exceptions.py IPublication.py 
	Publish.py __init__.py 
Log Message:
First part of the more interface-driven publisher.


=== Added File Zope3/lib/python/Zope/Publisher/BaseRequest.py ===


__version__='$Revision: 1.1.2.1 $'[11:-2]

from urllib import quote
from cgi import escape
from types import StringType


class RequestContainer:
    # TODO: add security assertion declaring this to be public

    def __init__(self, request):
        self.REQUEST = request



_marker=[]

class BaseRequest:
    """Represents a publishing request.
    
    This object provides access to request data. Request data may
    vary depending on the protocol used.

    Request objects are created by the object publisher and will be
    passed to published objects through the argument name, REQUEST.

    The request object is a mapping object that represents a
    collection of variable to value mappings.
    """

    # TODO: add security assertion declaring this to be public

    _file = None  # The request input stream
    common = {}   # Common request data

    # _held contains objects kept until the request is closed,
    # such as the database connection closer.
    _held = ()
    _auth = None  # Unverified credentials presented by the user.

    # URL is a string built up during traversal and url_quoted.
    # It does not include names built from publication.getDefault().
    URL = ''
    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.
    
    # PARENTS contains the objects traversed, innermost parents
    # first and the root object last.
    PARENTS = ()
    PUBLISHED = None          # Object traversed to
    AUTHENTICATED_USER = None # Authorized user object
    AUTHENTICATION_PATH = ''  # Unquoted path to parent of user folder

    def __init__(self, file, mapping, response):
        """
        The constructor should not raise errors.
        processInputs() should perform the unsafe initialization.
        """
        self._file = file
        self.steps = []
        self.quoted_steps = []
        self.other = {}
        self.other.update(mapping)
        self.response = response

    def close(self):
        self.other.clear()
        self._held = None  # Release held objects

    def processInputs(self):
        """Do any input processing that could raise errors
        """

    def __len__(self):
        return 1

    _key_handlers = {}

    def getRequest(self):
        return self
    _key_handlers['REQUEST'] = getRequest

    def getURL(self):
        return self.URL
    _key_handlers['URL'] = getURL

    def getBody(self):
        body = self.BODY
        if body is None:
            p = self._file.tell()
            self._file.seek(0)
            body = self._file.read()
            self._file.seek(p)
            self.BODY = body
        return body
    _key_handlers['BODY'] = getBody

    def getBodyFile(self):
        return self._file
    _key_handlers['BODYFILE'] = getBodyFile

    def __getitem__(self, key,
                    default=_marker, # Any special internal marker will do
                    ):
        """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. 
        
        """
        h = self._key_handlers.get(key, None)
        if h is not None:
            return h(self)

        v=self.other.get(key, _marker)
        if v is not _marker: return v
        v=self.common.get(key, default)
        if v is not _marker: return v
        
        raise KeyError, key

    def get(self, key, default=None):
        return self.__getitem__(key, default)

    def has_key(self,key):
        return self.get(key, _marker) is not _marker

    def keys(self):
        keys = {}
        #keys.update(self.common)
        keys.update(self.other)
        return keys.keys()

    def items(self):
        result = []
        get = self.get
        for k in self.keys():
            result.append((k, get(k)))
        return result

    def values(self):
        result = []
        get = self.get
        for k in self.keys():
            result.append(get(k))
        return result

    def __str__(self):
        L1 = self.items()
        L1.sort()
        return "\n".join(map(lambda item: "%s:\t%s" % item, L1))

    __repr__=__str__

    def _splitPath(self, path):
        # Split and clean up the path.
        if path[:1] == '/':  path = path[1:]
        if path[-1:] == '/': 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 _getDefaultView(self, object, m_name='_browser_default'):
##        """
##        returns (object, add_steps)
##        """
##        psteps = None
##        f = getattr(object, m_name, None)
##        if f is not None:
##            v = f(self)
##            if v is not None:
##                if type(v) is StringType:
##                    # One more traversal step.
##                    return object, (v,)
##                else:
##                    # Arbitrary additional traversal.
##                    o, add_steps = v
##                    if add_steps or o is not object:
##                        return o, add_steps
##        m = self._request_method
##        if m == 'GET' or m == 'POST':
##            default = 'index_html'
##        else:
##            default = m
##        if default and getattr(object, default, None) is not None:
##            return object, (default,)
##        return object, ()


    def changeTraversalStack(self, names):
        """
        Private.
        names should be in reverse.
        """
        self.to_traverse = names


    def setAuthenticatedUser(self, user):
        self.AUTHENTICATED_USER = user
        self.AUTHENTICATION_PATH = '/'.join(self.steps)


    def traverse(self, publication, object, path_str):
        """
        Traverses to an object and returns it.
        Private.
        """
        added_default = 0
        to_traverse = self._splitPath(path_str)

        if hasattr(object, '__of__'):
            # Try to bind the top-level object to the request.
            object = object.__of__(RequestContainer(self))

        parents = []
        parents.append(object)
        steps = self.steps
        self.quoted_steps = quoted_steps = map(quote, steps)
        to_traverse.reverse()
        self.to_traverse = to_traverse

        prev_object = None
        while 1:
            if object is not prev_object:
                # Invoke hooks (but not more than once).
                publication.invokeHooks(self, object)
                # A hook may have called changeTraversalStack().
                to_traverse = self.to_traverse
            prev_object = object

            if to_traverse:
                # Traverse to the next step.
                entry_name = to_traverse.pop()
                if entry_name:
                    qstep = quote(entry_name)
                    quoted_steps.append(qstep)
                    if not added_default:
                        # Build up the URL to the object, but not
                        # to the default view.
                        self.URL = '%s/%s' % (self.URL, qstep)
                    subobject = publication.traverseName(
                        self, object, entry_name)
                    publication.checkAuthorization(
                        self, object, entry_name, subobject)
                    object = subobject
                    parents.append(object)
                    steps.append(entry_name)

            elif not added_default:
                # Traverse to the default view, if any.
                added_default = 1
                object, add_steps = publication.getDefault(self, object)
                if add_steps:
                    to_traverse.extend(add_steps)

            else:
                # Finished traversal.
                break

        if added_default:
            self.response.setBase(self.URL)

        parents.reverse()
        self.PARENTS = parents
        self.PUBLISHED = object

        return object

    def supports_retry(self):
        return 0

    def _hold(self, object):
        """
        Holds a reference to an object to delay its destruction until mine
        """
        self._held = self._held + (object,)



=== Added File Zope3/lib/python/Zope/Publisher/BaseResponse.py ===

'''Response Output formatter

$Id: BaseResponse.py,v 1.1.2.1 2001/11/14 21:44:21 shane Exp $'''
__version__='$Revision: 1.1.2.1 $'[11:-2]

from zExceptions import Unauthorized

class BaseResponse:
    """Base Response Class

    What should be here?
    """
    debug_mode=None
    #_auth=None
    #_error_format='text/plain'
    
    def __init__(self, outstream, body='', headers=None,
                 status=None, cookies=None):
        self.outstream = outstream
        self.body = body
        if headers is None:
            headers = {}
        self.headers = headers
        self.status = status
        if cookies is None:
            cookies = {}
        self.cookies = cookies

    def setDebugMode(self, d):
        self.debug_mode = d
    
    def setStatus(self, status, reason=None):
        self.status = status

    def setHeader(self, name, value):
        self.headers[name] = value

    __setitem__ = setHeader

    def outputBody(self):
        """
        Output the response body.
        """
        self.outstream.write(str(self))

    def setBody(self, body):
        self.body = body

    def getStatus(self):
        'Returns the current HTTP status code as an integer. '
        return self.status

    def setCookie(self,name,value,**kw):
        '''
        Sets an HTTP cookie on the browser

        The response will include an HTTP header that sets a cookie on
        cookie-enabled browsers with a key "name" and value
        "value". This overwrites any previously set value for the
        cookie in the Response object.
        '''
        cookies=self.cookies
        if cookies.has_key(name):
            cookie=cookies[name]
        else: cookie=cookies[name]={}
        for k, v in kw.items():
            cookie[k]=v
        cookie['value']=value

    def getHeader(self, name):
         '''
         Gets a header value
         
         Returns the value associated with a HTTP return header, or
         "None" if no such header has been set in the response
         yet.
         '''
         return self.headers.get(name, None)

    def __getitem__(self, name):
        'Gets the value of an output header'
        return self.headers[name]

    def getBody(self):
        'Returns a string representing the currently set body.'
        return self.body

    def __str__(self):
        return str(self.body)

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, `self.body`)

    def flush(self):
        pass

    def write(self,data):
        """
        Implements the notional stream output interface.

        HTML data may be returned using a stream-oriented interface.
        This allows the browser to display partial results while
        computation of a response to proceed.

        The published object should first set any output headers or
        cookies on the response object.

        Note that published objects must not generate any errors
        after beginning stream-oriented output. 
        """
        self.body = self.body + data

##    def exception(self, fatal=0, info=None):
##        """Handle an exception.

##        The fatal argument indicates whether the error is fatal.

##        The info argument, if given should be a tuple with an
##        error type, value, and traceback.
##        """

##    def notFoundError(self, v=''):
##        """Generate an error indicating that an object was not found.
##        """
##        raise 'Not Found', v

##    def debugError(self, v=''):
##        """Raise an error with debigging info and in debugging mode"""
##        raise 'Debug Error', v

##    def badRequestError(self, v=''):
##        """Raise an error indicating something wrong with the request"""
##        raise 'Bad Request', v

##    def forbiddenError(self, v=''):
##        """Raise an error indicating that the request cannot be done"""
##        raise 'Forbidden', v

##    def unauthorized(self):
##        """Raise an eror indicating that the user was not authizated

##        Make sure to generate an appropriate challenge, as appropriate.
##        """
##        raise Unauthorized



=== Added File Zope3/lib/python/Zope/Publisher/Exceptions.py ===


from Zope.zExceptions import Unauthorized


class PublishingException (Exception):
    """
    """


class NotFound (PublishingException):

    def __init__(self, ob, name):
        self.ob = ob
        self.name = name

    def getOb(self):
        return self.ob

    def getName(self):
        return self.name


class DebugError (PublishingException):

    def __init__(self, s):
        self.s = s


class BadRequest (PublishingException):

    def __init__(self, s):
        self.s = s


class Redirect (PublishingException):

    def __init__(self, location):
        self.location = location


class Retry (PublishingException):
    """Raise this to retry a request
    """

    def __init__(self, t=None, v=None, tb=None):
        self._args = t, v, tb

    def reraise(self):
        t, v, tb = self._args
        if t is None: t=Retry
        if tb is None: raise t, v
        try: raise t, v, tb
        finally: tb=None




=== Added File Zope3/lib/python/Zope/Publisher/IPublication.py ===


from Interface import Interface

class IPublication (Interface):
    """
    Object publication framework.
    """
    # The order of the hooks mostly corresponds with the order in which
    # they are invoked.

    def getDebugMode(request):
        """
        Returns the debug mode setting.
        """

    def preTraversal(request):
        """
        Pre-traversal hook.
        """

    def getApplication(request):
        """
        Returns the object where traversal should commence.
        """

    def invokeHooks(request, object):
        """
        Invokes any traversal hooks associated with the object.
        """

    def traverseName(request, ob, name):
        """
        Traverses to the next object.
        """

    def checkAuthorization(request, ob, name, subob):
        """
        Performs idenitification, authentication, and authorization.
        Should call request.setAuthenticatedUser().
        """

    def getDefault(request, ob):
        """
        Allows a default view to be added to traversal.
        Returns (ob, steps_reversed).
        """

    def postTraversal(request, ob):
        """
        Post-traversal hook.
        """

    def publish(request, ob):
        """
        Publishes the object, usually by calling it.
        """

    def postPublish(request):
        """
        Post-publishing hook (if publishing was successful).
        """

    def handleException(request, t, v, tb):
        """
        Either:
        - sets the body of request.response,
        - raises a Retry exception, or
        - throws another exception, which is a Bad Thing.
        """



=== Added File Zope3/lib/python/Zope/Publisher/Publish.py ===


__doc__="""Python Object Publisher -- Publish Python objects on web servers

$Id: Publish.py,v 1.1.2.1 2001/11/14 21:44:21 shane Exp $"""
__version__='$Revision: 1.1.2.1 $'[11:-2]

import sys, os


def traverseAndPublish(publication, request):

##    (bobo_before, bobo_after, object, realm, debug_mode, err_hook,
##     validated_hook, transactions_manager)= get_module_info(module_name)

    parents = None

    try:
        request.processInputs()
        response = request.response
    
        if debug_mode:
            response.setDebugMode(debug_mode)

        publication.preTraversal(request)
        #if transactions_manager: transactions_manager.begin()
    
        object = publication.getApplication()
        path_str = request.get('PATH_INFO', '').strip()
        object = request.traverse(publication, object, path_str)
    
        publication.postTraversal(request, object)
        result = publication.publish(request, object)
##        if transactions_manager:
##            transactions_manager.recordMetaData(object, request)
    
##        result=mapply(object, request.args, request,
##                      call_object,1,
##                      missing_name, 
##                      dont_publish_class,
##                      request, bind=1)

        if result is not response:
            response.setBody(result)

        publication.postPublish(request)
##        if transactions_manager: transactions_manager.commit()

        return response
    except:
        try:
            t, v, tb = sys.exc_info()
            try:
                publication.handleException(request, t, v, tb)
            except Retry:
                # The exception handler requested a retry.
                if not request.supports_retry():
                    # Can't retry.  Restore the original exception.
                    raise t, v, tb
                newrequest = request.retry()
                request.close()  # Free resources held by the request.
                try:
                    tb = None
                    return traverseAndPublish(publication, newrequest)
                finally:
                    newrequest.close()
        finally:
            del tb  # Avoid circular ref.


def publishRequest(publication, request):
    to_raise = None
    try:
        try:
            response = traverseAndPublish(publication, request)
        except SystemExit, v:
            to_raise = sys.exc_info()
            publication.handleException(
                request, to_raise[0], to_raise[1], to_raise[2])
        except:
            to_raise = sys.exc_info()
            publication.handleException(
                request, to_raise[0], to_raise[1], to_raise[2])

        response.outputBody()

    finally:
        if request is not None:
            request.close()

    if to_raise:
        try:
            raise to_raise[0], to_raise[1], to_raise[2]
        finally:
            to_raise = None

    return response.getStatus()
    



##def publish_module(module_name,
##                   stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
##                   environ=os.environ, debug=0, request=None, response=None):
##    must_die=0
##    status=200
##    after_list=[None]
##    try:
##        try:
##            if response is None:
##                response=Response(stdout=stdout, stderr=stderr)
##            else:
##                stdout=response.stdout

##            if request is None:
##                request=Request(stdin, environ, response)

##            response = publish(request, module_name, after_list, debug=debug)
##        except SystemExit, v:
##            must_die=sys.exc_info()
##            request.response.exception(must_die)
##        except ImportError, v:
##            if type(v) is type(()) and len(v)==3: must_die=v
##            elif hasattr(sys, 'exc_info'): must_die=sys.exc_info()
##            else: must_die = SystemExit, v, sys.exc_info()[2]
##            request.response.exception(1, v)
##        except:
##            request.response.exception()
##            status=response.getStatus()

##        if response:
##            outputBody=getattr(response, 'outputBody', None)
##            if outputBody is not None:
##                outputBody()
##            else:
##                response=str(response)
##                if response: stdout.write(response)

##        # The module defined a post-access function, call it
##        if after_list[0] is not None: after_list[0]()

##    finally:
##        if request is not None: request.close()

##    if must_die:
##        try: raise must_die[0], must_die[1], must_die[2]
##        finally: must_die=None

##    return status


##_l=allocate_lock()
##def get_module_info(module_name, modules={},
##                    acquire=_l.acquire,
##                    release=_l.release,
##                    ):

##    if modules.has_key(module_name): return modules[module_name]

##    if module_name[-4:]=='.cgi': module_name=module_name[:-4]

##    acquire()
##    tb=None
##    try:
##        try:
##            module=__import__(module_name, globals(), globals(), ('__doc__',))

##            realm=module_name

##            # Let the app specify a realm
##            if hasattr(module,'__bobo_realm__'):
##                realm=module.__bobo_realm__
##            elif os.environ.has_key('Z_REALM'):
##                realm=os.environ['Z_REALM']
##            elif os.environ.has_key('BOBO_REALM'):
##                realm=os.environ['BOBO_REALM']
##            else: realm=module_name

##            # Check for debug mode
##            debug_mode=None
##            if hasattr(module,'__bobo_debug_mode__'):
##                debug_mode=not not module.__bobo_debug_mode__
##            else:

##                z1 = os.environ.get('Z_DEBUG_MODE','')
##                z2 = os.environ.get('BOBO_DEBUG_MODE','')
            
##                if z1.lower() in ('yes','y') or z1.isdigit():
##                    debug_mode = 1
##                elif z2.lower() in ('yes','y') or z2.isdigit():
##                    debug_mode = 1


##            if hasattr(module,'__bobo_before__'):
##                bobo_before=module.__bobo_before__
##            else: bobo_before=None

##            if hasattr(module,'__bobo_after__'): bobo_after=module.__bobo_after__
##            else: bobo_after=None

##            if hasattr(module,'bobo_application'):
##                object=module.bobo_application
##            elif hasattr(module,'web_objects'):
##                object=module.web_objects
##            else: object=module

##            error_hook=getattr(module,'zpublisher_exception_hook', None)
##            validated_hook=getattr(module,'zpublisher_validated_hook', None)

##            transactions_manager=getattr(
##                module,'zpublisher_transactions_manager', None)
##            if not transactions_manager:
##                try: get_transaction()
##                except: pass
##                else:
##                    # Create a default transactions manager for use
##                    # by software that uses ZPublisher and ZODB but
##                    # not the rest of Zope.
##                    transactions_manager = DefaultTransactionsManager()

##            info= (bobo_before, bobo_after, object, realm, debug_mode,
##                   error_hook, validated_hook, transactions_manager)

##            modules[module_name]=modules[module_name+'.cgi']=info
            
##            return info
##        except:
##            t,v,tb=sys.exc_info()
##            v=str(v)
##            raise ImportError, (t, v), tb
##    finally:
##        tb=None
##        release()


##class DefaultTransactionsManager:
##    def begin(self): get_transaction().begin()
##    def commit(self): get_transaction().commit()
##    def abort(self): get_transaction().abort()
##    def recordMetaData(self, object, request):
##        # Is this code needed?
##        request_get = request.get
##        T=get_transaction()
##        T.note(request_get('PATH_INFO'))
##        auth_user=request_get('AUTHENTICATED_USER',None)
##        if auth_user is not None:
##            T.setUser(auth_user, request_get('AUTHENTICATION_PATH'))
        

### ZPublisher profiler support
### ---------------------------

##if os.environ.get('PROFILE_PUBLISHER', None):

##    import profile, pstats

##    _pfile=os.environ['PROFILE_PUBLISHER']
##    _plock=allocate_lock()
##    _pfunc=publish_module
##    _pstat=None

##    def pm(module_name, stdin, stdout, stderr, 
##           environ, debug, request, response):
##        try:
##            r=_pfunc(module_name, stdin=stdin, stdout=stdout, 
##                     stderr=stderr, environ=environ, debug=debug, 
##                     request=request, response=response)
##        except: r=None
##        sys._pr_=r

##    def publish_module(module_name, stdin=sys.stdin, stdout=sys.stdout, 
##                       stderr=sys.stderr, environ=os.environ, debug=0, 
##                       request=None, response=None):
##        global _pstat
##        _plock.acquire()
##        try:
##            if request is not None:
##                path_info=request.get('PATH_INFO')
##            else: path_info=environ.get('PATH_INFO')
##            if path_info[-14:]=='manage_profile':
##                return _pfunc(module_name, stdin=stdin, stdout=stdout, 
##                              stderr=stderr, environ=environ, debug=debug, 
##                              request=request, response=response)
##            pobj=profile.Profile()
##            pobj.runcall(pm, module_name, stdin, stdout, stderr, 
##                         environ, debug, request, response)
##            result=sys._pr_
##            pobj.create_stats()
##            if _pstat is None:
##                _pstat=sys._ps_=pstats.Stats(pobj)
##            else: _pstat.add(pobj)
##        finally:
##            _plock.release()

##        if result is None:
##            try:
##                error=sys.exc_info()
##                file=open(_pfile, 'w')
##                file.write(
##                "See the url "
##                "http://www.python.org/doc/current/lib/module-profile.html"
##                "\n for information on interpreting profiler statistics.\n\n"
##                    )
##                sys.stdout=file
##                _pstat.strip_dirs().sort_stats('cumulative').print_stats(250)
##                _pstat.strip_dirs().sort_stats('time').print_stats(250)
##                file.flush()
##                file.close()
##            except: pass
##            raise error[0], error[1], error[2]
##        return result



=== Added File Zope3/lib/python/Zope/Publisher/__init__.py ===


"""
Notes:

provideExceptionHandler(BrowserPublish, Redirect, HTTPRedirector)
"""