[Zope3-checkins] CVS: Zope3/src/zope/publisher - __init__.py:1.2 base.py:1.2 browser.py:1.2 configure.zcml:1.2 http.py:1.2 maybe_lock.py:1.2 meta.zcml:1.2 normal.clb:1.2 publish.py:1.2 vfs.py:1.2 xmlrpc.py:1.2
Jim Fulton
jim@zope.com
Wed, 25 Dec 2002 09:15:49 -0500
Update of /cvs-repository/Zope3/src/zope/publisher
In directory cvs.zope.org:/tmp/cvs-serv20790/src/zope/publisher
Added Files:
__init__.py base.py browser.py configure.zcml http.py
maybe_lock.py meta.zcml normal.clb publish.py vfs.py xmlrpc.py
Log Message:
Grand renaming:
- Renamed most files (especially python modules) to lower case.
- Moved views and interfaces into separate hierarchies within each
project, where each top-level directory under the zope package
is a separate project.
- Moved everything to src from lib/python.
lib/python will eventually go away. I need access to the cvs
repository to make this happen, however.
There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.
=== Zope3/src/zope/publisher/__init__.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/__init__.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.
=== Zope3/src/zope/publisher/base.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/base.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,495 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+'''Response Output formatter
+
+$Id$
+'''
+
+
+import traceback
+from cStringIO import StringIO
+
+from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
+from zope.exceptions import NotFoundError
+
+from zope.publisher.interfaces import IPublication
+from zope.publisher.interfaces import NotFound, DebugError, Unauthorized
+from zope.publisher.interfaces import IApplicationResponse
+from zope.publisher.interfaces import IApplicationRequest
+from zope.publisher.interfaces import IPublicationRequest
+from zope.publisher.interfaces import IPublisherResponse
+from zope.publisher.interfaces import IPublisherRequest
+
+from zope.publisher.publish import mapply
+
+
+class IResponse(IPublisherResponse, IApplicationResponse):
+ """The basic response contract
+ """
+
+
+class BaseResponse(object):
+ """Base Response Class
+ """
+
+ __slots__ = (
+ '_body', # The response body
+ '_outstream', # The output stream
+ )
+
+ __implements__ = IResponse
+
+
+ def __init__(self, outstream):
+ self._body = ''
+ self._outstream = outstream
+
+ def outputBody(self):
+ 'See IPublisherResponse'
+ self._outstream.write(self._getBody())
+
+ def setBody(self, body):
+ 'See IPublisherResponse'
+ self._body = body
+
+ # This method is not part of this interface
+ def _getBody(self):
+ 'Returns a string representing the currently set body.'
+ return self._body
+
+ def handleException(self, exc_info):
+ 'See IPublisherResponse'
+ traceback.print_exception(
+ exc_info[0], exc_info[1], exc_info[2], 100, self)
+
+ def internalError(self):
+ 'See IPublisherResponse'
+ pass
+
+ def retry(self):
+ 'See IPublisherResponse'
+ return self.__class__(self.outstream)
+
+ def write(self, string):
+ 'See IApplicationResponse'
+ self._body += string
+
+class RequestDataGetter(object):
+
+ __implements__ = IReadMapping
+
+ def __init__(self, request):
+ self.__get = getattr(request, self._gettrname)
+
+ def __getitem__(self, name):
+ return self.__get(name)
+
+ def get(self, name, default=None):
+ return self.__get(name, default)
+
+ def __contains__(self, key):
+ lookup = self.get(key, self)
+ return lookup is not self
+
+ has_key = __contains__
+
+class RequestDataMapper(object):
+
+ __implements__ = IEnumerableMapping
+
+ def __init__(self, request):
+ self.__map = getattr(request, self._mapname)
+
+ def __getitem__(self, name):
+ return self.__map[name]
+
+ def get(self, name, default=None):
+ return self.__map.get(name, default)
+
+ def __contains__(self, key):
+ lookup = self.get(key, self)
+ return lookup is not self
+
+ has_key = __contains__
+
+ def keys(self): return self.__map.keys()
+ def items(self): return self.__map.items()
+ def values(self): return self.__map.values()
+ def __len__(self): return len(self.__map)
+
+class RequestDataProperty(object):
+
+ def __init__(self, gettr_class):
+ self.__gettr_class = gettr_class
+
+ def __get__(self, request, rclass=None):
+ if request is not None:
+ return self.__gettr_class(request)
+
+ def __set__(*args):
+ raise AttributeError, 'Unassignable attribute'
+
+
+
+class IRequest(IPublisherRequest, IPublicationRequest, IApplicationRequest):
+ """The basic request contract
+ """
+
+_marker = object()
+
+class RequestEnvironment(RequestDataMapper):
+ _mapname = '_environ'
+
+class BaseRequest(object):
+ """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.
+ """
+
+ __implements__ = IRequest
+
+ __slots__ = (
+ '_held', # Objects held until the request is closed
+ '_traversed_names', # The names that have been traversed
+ '_traversal_stack', # Names to be traversed, in reverse order
+ '_environ', # The request environment variables
+ '_response', # The response
+ '_args', # positional arguments
+ '_body_instream', # input stream
+ '_body', # The request body as a string
+ '_publication', # publication object
+ '_presentation_skin', # View skin
+ 'user' # request user, set by publication
+ )
+
+ environment = RequestDataProperty(RequestEnvironment)
+
+ def __init__(self, body_instream, outstream, environ, response=None,
+ positional=()):
+ self._traversal_stack = []
+ self._traversed_names = []
+ self._environ = environ
+
+ self._args = positional
+ if response is None:
+ self._response = self._createResponse(outstream)
+ else:
+ self._response = response
+ self._body_instream = body_instream
+ self._held = ()
+ self.user = None
+
+ def _getPublication(self):
+ 'See IPublisherRequest'
+ return getattr(self, '_publication', None)
+
+ publication = property(_getPublication)
+
+
+ def processInputs(self):
+ 'See IPublisherRequest'
+ # Nothing to do here
+
+ def retry(self):
+ 'See IPublisherRequest'
+ raise TypeError('Retry is not supported')
+
+ def setPublication(self, pub):
+ 'See IPublisherRequest'
+ self._publication = pub
+
+ def supportsRetry(self):
+ 'See IPublisherRequest'
+ return 0
+
+ def traverse(self, object):
+ 'See IPublisherRequest'
+
+ publication = self.publication
+
+ traversal_stack = self._traversal_stack
+ traversed_names = self._traversed_names
+
+ prev_object = None
+ while 1:
+ if object is not prev_object:
+ # Invoke hooks (but not more than once).
+ publication.callTraversalHooks(self, object)
+
+ prev_object = object
+
+ if traversal_stack:
+ # Traverse to the next step.
+ entry_name = traversal_stack.pop()
+ subobject = publication.traverseName(
+ self, object, entry_name)
+ traversed_names.append(entry_name)
+ object = subobject
+ else:
+ # Finished traversal.
+ break
+
+ return object
+
+ def close(self):
+ 'See IPublicationRequest'
+ self._held = None
+ self._response = None
+ self._body_instream = None
+ self._publication = None
+
+ def getPositionalArguments(self):
+ 'See IPublicationRequest'
+ return self._args
+
+ def _getResponse(self):
+ return self._response
+
+ response = property(_getResponse)
+
+ def getTraversalStack(self):
+ 'See IPublicationRequest'
+ return list(self._traversal_stack) # Return a copy
+
+ def hold(self, object):
+ 'See IPublicationRequest'
+ self._held = self._held + (object,)
+
+ def setTraversalStack(self, stack):
+ 'See IPublicationRequest'
+ self._traversal_stack[:] = list(stack)
+
+ def setViewSkin(self, skin):
+ 'See IPublicationRequest'
+ self._presentation_skin = skin
+
+ def getPresentationSkin(self):
+ 'See IPresentationRequest'
+ return getattr(self, '_presentation_skin', '')
+
+ def getPresentationType(self):
+ 'See IPresentationRequest'
+ return getattr(self, '_presentation_type', None)
+
+ # This is not part of the interface:
+ def setViewType(self, viewtype):
+ '''Set the view type.
+
+ This method will normally only be called in tests, which will allow
+ us to use a simpler Request set-up.'''
+
+ # XXX This will probably go away
+
+ self._presentation_type = viewtype
+
+ def _getBody(self):
+ body = getattr(self, '_body', None)
+ if body is None:
+ s = self._body_instream
+ if s is None:
+ return None # XXX what should be returned here?
+ p = s.tell()
+ s.seek(0)
+ body = s.read()
+ s.seek(p)
+ self._body = body
+ return body
+
+ body = property(_getBody)
+
+ def _getBodyFile(self):
+ 'See IApplicationRequest'
+ return self._body_instream
+
+ bodyFile = property(_getBodyFile)
+
+ def __len__(self):
+ 'See Interface.Common.Mapping.IEnumerableMapping'
+ return len(self.keys())
+
+ def items(self):
+ 'See Interface.Common.Mapping.IEnumerableMapping'
+ result = []
+ get = self.get
+ for k in self.keys():
+ result.append((k, get(k)))
+ return result
+
+ def keys(self):
+ 'See Interface.Common.Mapping.IEnumerableMapping'
+ return self._environ.keys()
+
+ def values(self):
+ 'See Interface.Common.Mapping.IEnumerableMapping'
+ result = []
+ get = self.get
+ for k in self.keys():
+ result.append(get(k))
+ return result
+
+ def __getitem__(self, key):
+ 'See Interface.Common.Mapping.IReadMapping'
+ result = self.get(key, _marker)
+ if result is _marker:
+ raise KeyError, key
+ else:
+ return result
+
+ def get(self, key, default=None):
+ 'See Interface.Common.Mapping.IReadMapping'
+
+ result = self._environ.get(key, self)
+ if result is not self: return result
+
+ return default
+
+ def __contains__(self, key):
+ 'See Interface.Common.Mapping.IReadMapping'
+ lookup = self.get(key, self)
+ return lookup is not self
+
+ has_key = __contains__
+
+ def _createResponse(self, outstream):
+ # Should be overridden by subclasses
+ return BaseResponse(outstream)
+
+ def __nonzero__(self):
+ # This is here to avoid calling __len__ for boolean tests
+ return 1
+
+ def __str__(self):
+ L1 = self.items()
+ L1.sort()
+ return "\n".join(map(lambda item: "%s:\t%s" % item, L1))
+
+ def __repr__(self):
+ # Returns a *short* string.
+ return '<%s instance at 0x%x, URL=%s>' % (
+ str(self.__class__), id(self), `self.URL`)
+
+ def _setupPath_helper(self, attr):
+ path = self.get(attr, "/").strip()
+ if path.endswith('/'):
+ path = path[:-1] # XXX Why? Not sure
+ self._endswithslash = 1
+ else:
+ self._endswithslash = 0
+
+ clean = []
+ for item in path.split('/'):
+ if not item or item == '.':
+ continue
+ elif item == '..':
+ try: del clean[-1]
+ except IndexError:
+ raise NotFoundError('..')
+ else: clean.append(item)
+
+ clean.reverse()
+ self.setTraversalStack(clean)
+
+ self._path_suffix = None
+
+class TestRequest(BaseRequest):
+
+ __slots__ = ('_presentation_type', )
+
+ def __init__(self, path, body_instream=None, outstream=None, environ=None):
+ if environ is None:
+ environ = {}
+ environ['PATH_INFO'] = path
+ if body_instream is None:
+ body_instream = StringIO('')
+ if outstream is None:
+ outstream = StringIO()
+
+ super(TestRequest, self).__init__(body_instream, outstream, environ)
+
+
+class DefaultPublication:
+
+ __implements__ = IPublication
+
+ require_docstrings = 1
+
+ def __init__(self, app):
+ self.app = app
+
+ def beforeTraversal(self, request):
+ # 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
+
+ def callTraversalHooks(self, request, ob):
+ pass
+
+ def traverseName(self, request, ob, name, check_auth=1):
+ if name.startswith('_'):
+ raise Unauthorized("Name %s begins with an underscore" % `name`)
+ if hasattr(ob, name):
+ subob = getattr(ob, name)
+ else:
+ try:
+ subob = ob[name]
+ except (KeyError, IndexError,
+ 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')
+ return subob
+
+ def getDefaultTraversal(self, request, ob):
+ return ob, ()
+
+ def afterTraversal(self, request, ob):
+ pass
+
+ def callObject(self, request, ob):
+ return mapply(ob, request.getPositionalArguments(), request)
+
+ def afterCall(self, request):
+ pass
+
+ def handleException(self, object, request, exc_info, retry_allowed=1):
+ # Let the response handle it as best it can.
+ request.response.handleException(exc_info)
+
+
+class TestPublication(DefaultPublication):
+
+ def traverseName(self, request, ob, name, check_auth=1):
+ if hasattr(ob, name):
+ subob = getattr(ob, name)
+ else:
+ try:
+ subob = ob[name]
+ except (KeyError, IndexError,
+ TypeError, AttributeError):
+ raise NotFound(ob, name, request)
+ return subob
=== Zope3/src/zope/publisher/browser.py 1.1 => 1.2 === (779/879 lines abridged)
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/browser.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,876 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+__version__='$Revision$'[11:-2]
+
+import re
+from types import ListType, TupleType
+
+__ArrayTypes = (ListType, TupleType)
+
+def field2string(v):
+ if hasattr(v,'read'): v=v.read()
+ else: v=str(v)
+ return v
+
+def field2text(v, nl=re.compile('\r\n|\n\r').search):
+ if hasattr(v,'read'): v=v.read()
+ else: v=str(v)
+ mo = nl(v)
+ if mo is None: return v
+ l = mo.start(0)
+ r=[]
+ s=0
+ while l >= s:
+ r.append(v[s:l])
+ s=l+2
+ mo=nl(v,s)
+ if mo is None: l=-1
+ else: l=mo.start(0)
+
+ r.append(v[s:])
+
+ return '\n'.join(r)
+
+def field2required(v):
+ if hasattr(v,'read'): v=v.read()
+ else: v=str(v)
[-=- -=- -=- 779 lines omitted -=- -=- -=-]
+ def __wrapInHTML(self, title, content):
+ t = escape(title)
+ return (
+ "<html><head><title>%s</title></head>\n"
+ "<body><h2>%s</h2>\n"
+ "%s\n"
+ "</body></html>\n" %
+ (t, t, content)
+ )
+
+
+ def __insertBase(self, body):
+ # Only insert a base tag if content appears to be html.
+ content_type = self.getHeader('content-type', '')
+ if content_type and not is_text_html(content_type):
+ return body
+
+ if getattr(self, '_base', ''):
+ if body:
+ match = start_of_header_search(body)
+ if match is not None:
+ index = match.start(0) + len(match.group(0))
+ ibase = base_re_search(body)
+ if ibase is None:
+ body = ('%s\n<base href="%s" />\n%s' %
+ (body[:index], self._base, body[index:]))
+ return body
+
+ def getBase(self):
+ return getattr(self, '_base', '')
+
+ def setBase(self, base):
+ self._base = base
+
+ def redirect(self, location, status=302):
+ base = getattr(self, '_base', '')
+ if base and isRelative(str(location)):
+ l = base.rfind('/')
+ if l >= 0:
+ base = base[:l+1]
+ else:
+ base += '/'
+ location = base + location
+
+ super(BrowserResponse, self).redirect(location, status)
+
+
+
+def is_text_html(content_type):
+ return content_type.startswith('text/html')
=== Zope3/src/zope/publisher/configure.zcml 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/configure.zcml Wed Dec 25 09:15:18 2002
@@ -0,0 +1,24 @@
+<zopeConfigure
+ xmlns='http://namespaces.zope.org/zope'
+>
+ <include package=".HTTP" />
+
+</zopeConfigure>
+
+<zopeConfigure
+ xmlns='http://namespaces.zope.org/zope'
+>
+
+ <content class="zope.publisher.http.HTTPRequest">
+ <require
+ permission="zope.View"
+ interface="zope.publisher.interfaces.http.IHTTPApplicationRequest"/>
+ </content>
+
+ <content class="zope.publisher.http.URLGetter">
+ <require
+ permission="zope.View"
+ attributes="get __getitem__ __str__" />
+ </content>
+
+</zopeConfigure>
=== Zope3/src/zope/publisher/http.py 1.1 => 1.2 === (931/1031 lines abridged)
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/http.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,1028 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import re, time, random
+from urllib import quote, splitport
+from types import StringType
+
+from zope.publisher.base import BaseRequest
+from zope.exceptions import NotFoundError
+
+
+from zope.publisher.interfaces.http import IHTTPCredentials
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.publisher.interfaces.http import IHTTPApplicationRequest
+
+from zope.publisher.base \
+ import RequestDataProperty, RequestDataMapper, RequestDataGetter
+
+# Default Encoding
+ENCODING = 'UTF-8'
+
+class CookieMapper(RequestDataMapper):
+ _mapname = '_cookies'
+
+class HeaderGetter(RequestDataGetter):
+ _gettrname = 'getHeader'
+
+_marker = object()
+
+class URLGetter:
+
+ def __init__(self, request):
[-=- -=- -=- 931 lines omitted -=- -=- -=-]
+ if y[1] == 'utf-8':
+ return 1
+ if x[1] == 'utf-8':
+ return -1
+ return cmp(y, x)
+
+
+class HTTPCharsets:
+
+ __implements__ = IUserPreferredCharsets
+
+ def __init__(self, request):
+ self.request = request
+
+ def getPreferredCharsets(self):
+ '''See interface IUserPreferredCharsets'''
+ charsets = []
+ sawstar = sawiso88591 = 0
+ for charset in self.request.get('HTTP_ACCEPT_CHARSET', '').split(','):
+ charset = charset.strip().lower()
+ if charset:
+ if ';' in charset:
+ charset, quality = charset.split(';')
+ if not quality.startswith('q='):
+ # not a quality parameter
+ quality = 1.0
+ else:
+ try:
+ quality = float(quality[2:])
+ except ValueError:
+ continue
+ else:
+ quality = 1.0
+ if quality == 0.0:
+ continue
+ if charset == '*':
+ sawstar = 1
+ if charset == 'iso-8859-1':
+ sawiso88591 = 1
+ charsets.append((quality, charset))
+ # Quoting RFC 2616, $14.2: If no "*" is present in an Accept-Charset
+ # field, then all character sets not explicitly mentioned get a
+ # quality value of 0, except for ISO-8859-1, which gets a quality
+ # value of 1 if not explicitly mentioned.
+ if not sawstar and not sawiso88591:
+ charsets.append((1.0, 'iso-8859-1'))
+ # UTF-8 is **always** preferred over anything else.
+ # XXX Please give more details as to why!
+ charsets.sort(sort_charsets)
+ return [c[1] for c in charsets]
=== Zope3/src/zope/publisher/maybe_lock.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/maybe_lock.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,21 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+__version__='$Revision$'[11:-2]
+
+# Waaaa, I wish I didn't have to work this hard.
+try: from thread import allocate_lock
+except:
+ class allocate_lock:
+ def acquire(*args): pass
+ def release(*args): pass
=== Zope3/src/zope/publisher/meta.zcml 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/meta.zcml Wed Dec 25 09:15:18 2002
@@ -0,0 +1,8 @@
+<zopeConfigure
+ xmlns='http://namespaces.zope.org/zope'
+ xmlns:browser='http://namespaces.zope.org/browser'
+>
+
+ <include package=".VFS" file="meta.zcml" />
+
+</zopeConfigure>
=== Zope3/src/zope/publisher/normal.clb 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/normal.clb Wed Dec 25 09:15:18 2002
@@ -0,0 +1,108 @@
+Publisher framework base collaboration
+
+ Participants:
+
+ publisher:IPublisher
+
+ request:IPublisherRequest
+
+ response:IPublicationResponse = request.getResponse()
+
+ publication:IPublication = request.getPublication()
+
+
+ Values:
+
+ root
+ "the top-level object"
+
+ foo
+ "an object obtaines by traversing the root with 'foo'"
+
+ bar
+ "an object obtaines by traversing the root with 'bar'"
+
+ result
+ "The result of calling bar"
+
+
+ Scenario: Normal, path = foo/bar
+ "Show normal publishing of a simple path without errors"
+
+ publisher.publish(request)
+
+ request.processInputs()
+
+ publication.beforeTraversal(request)
+
+ publication.getApplication(request)
+ "Get the root object to be traversed"
+
+ request.traverse(root)
+
+ publication.callTraversalHooks(request, root)
+ '''Call any "before traversal step" hooks. These hooks
+ should be called before traversing an object for the
+ first time. If the same object is traversed more than
+ once, the hook will still only be called the first
+ time.
+
+ The last constraint is probably important to get
+ virtual host semantics rigfht. :)
+ '''
+
+ publication.traverseName(request, root, 'foo')
+
+ publication.callTraversalHooks(request, foo)
+
+ publication.traverseName(request, foo, 'bar')
+
+ return bar
+
+ publication.afterTraversal(request, bar)
+
+ publication.callObject(request, bar)
+
+ return result
+
+ response.setBody(result)
+
+ publication.afterCall(request)
+
+ response.outputBody()
+
+ request.close()
+
+
+ Scenario: Error during application call, path = foo
+ "Show what heppens when the main application code raises an error"
+
+ publisher.publish(request)
+
+ request.processInputs()
+
+ publication.beforeTraversal(request)
+
+ publication.getApplication(request)
+
+ request.traverse(root)
+
+ publication.callTraversalHooks(request, root)
+
+ publication.traverseName(request, root, 'foo')
+
+ return foo
+
+ publication.afterTraversal(request, foo)
+
+ publication.callObject(request, foo)
+
+ raise AttributeError, 'spam'
+
+
+ publication.handleException()
+
+ response.outputBody()
+
+ request.close()
+
=== Zope3/src/zope/publisher/publish.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/publish.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,182 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Provide an apply-like facility that works with any mapping object
+"""
+
+from zope.proxy.introspection import removeAllProxies
+
+_marker = [] # Create a new marker object.
+
+
+def unwrapMethod(object):
+ """ object -> ( unwrapped, wrapperCount )
+
+ Unwrap 'object' until we get to a real function, counting the
+ number of unwrappings.
+
+ Bail if we find a class or something we can't
+ idendify as callable.
+ """
+ wrapperCount = 0
+ unwrapped = object
+ for i in range(10):
+ bases = getattr(unwrapped, '__bases__', None)
+ if bases is not None:
+ raise TypeError, "mapply() can not call class constructors"
+
+ im_func = getattr(unwrapped, 'im_func', None)
+ if im_func is not None:
+ unwrapped = im_func
+ wrapperCount += 1
+ continue
+
+ func_code = getattr(unwrapped, 'func_code', None)
+ if func_code is not None:
+ break
+
+ __call__ = getattr(unwrapped, '__call__' , None)
+ if __call__ is not None:
+ unwrapped = unwrapped.__call__
+ else:
+ raise TypeError, "mapply() can not call %s" % `object`
+
+ else:
+ raise TypeError(
+ "couldn't find callable metadata, mapply() error on %s"%`object`
+ )
+
+ return unwrapped, wrapperCount
+
+def mapply(object, positional=(), request={}):
+ __traceback_info__ = object
+
+ # we need deep access for intrspection. Waaa.
+ unwrapped = removeAllProxies(object)
+
+ unwrapped, wrapperCount = unwrapMethod(unwrapped)
+
+ code = unwrapped.func_code
+ defaults = unwrapped.func_defaults
+ names = code.co_varnames[wrapperCount:code.co_argcount]
+
+ nargs = len(names)
+ if positional:
+ args = list(positional)
+ if len(args) > nargs:
+ given = len(args)
+ if wrapperCount:
+ given = given + wrapperCount
+ raise TypeError, (
+ '%s() takes at most %d argument%s(%d given)' % (
+ getattr(unwrapped, '__name__', repr(object)), code.co_argcount,
+ (code.co_argcount > 1 and 's ' or ' '), given))
+ else:
+ args = []
+
+ get = request.get
+ if defaults:
+ nrequired = len(names) - (len(defaults))
+ else:
+ nrequired = len(names)
+ for index in range(len(args), nargs):
+ name = names[index]
+ v = get(name, _marker)
+ if v is _marker:
+ if name == 'REQUEST':
+ v = request
+ elif index < nrequired:
+ raise TypeError, 'Missing argument to %s(): %s' % (
+ getattr(unwrapped, '__name__', repr(object)), name)
+ else:
+ v = defaults[index-nrequired]
+ args.append(v)
+
+ args = tuple(args)
+ return object(*args)
+
+
+"""
+Python Object Publisher -- Publish Python objects on web servers
+
+$Id$
+"""
+
+
+import sys, os
+from zope.publisher.interfaces import Retry
+
+def publish(request, handle_errors=1):
+ try: # finally to clean up to_raise and close request
+ to_raise = None
+ while 1:
+ publication = request.publication
+ try:
+ try:
+ object = None
+ try:
+ request.processInputs()
+ publication.beforeTraversal(request)
+
+ object = publication.getApplication(request)
+ object = request.traverse(object)
+ publication.afterTraversal(request, object)
+
+ result = publication.callObject(request, object)
+ response = request.response
+ if result is not response:
+ response.setBody(result)
+
+ publication.afterCall(request)
+
+ except:
+ publication.handleException(object, request, sys.exc_info(), 1)
+
+ if not handle_errors:
+ raise
+
+ break # Successful.
+
+ except Retry, retryException:
+ if request.supportsRetry():
+ # Create a copy of the request and use it.
+ newrequest = request.retry()
+ request.close()
+ request = newrequest
+ elif handle_errors:
+ # Output the original exception.
+ publication = request.publication
+ publication.handleException(
+ object, request, retryException.getOriginalException(), 0)
+ break
+ else:
+ raise
+
+ except:
+ # Bad exception handler or retry method.
+ # Re-raise after outputting the response.
+ if handle_errors:
+ request.response.internalError()
+ to_raise = sys.exc_info()
+ break
+ else:
+ raise
+
+ response = request.response
+ response.outputBody()
+ if to_raise is not None:
+ raise to_raise[0], to_raise[1], to_raise[2]
+
+ finally:
+ to_raise = None # Avoid circ. ref.
+ request.close() # Close database connections, etc.
=== Zope3/src/zope/publisher/vfs.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/vfs.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,229 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.publisher.base import BaseResponse
+
+
+class VFSResponse(BaseResponse):
+ """VFS response
+ """
+ __slots__ = (
+ '_exc',
+ )
+
+ def setBody(self, body):
+ """Sets the body of the response
+
+ It is very important to note that in this case the body may
+ not be just a astring, but any Python object.
+ """
+
+ self._body = body
+
+
+ def outputBody(self):
+ 'See IPublisherResponse'
+ pass
+
+
+ def getResult(self):
+ if getattr(self, '_exc', None) is not None:
+ raise self._exc[0], self._exc[1]
+ return self._getBody()
+
+
+ def handleException(self, exc_info):
+ self._exc = exc_info[:2]
+ # import traceback
+ # traceback.print_exc()
+
+
+"""
+
+$Id$
+"""
+__metaclass__ = type # All classes are new style when run with Python 2.2+
+
+from zope.publisher.interfaces.vfs import IVFSView
+
+class VFSView:
+
+ __implements__ = IVFSView
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+
+"""
+
+$Id$
+"""
+
+from zope.publisher.base import BaseRequest
+from zope.publisher.interfaces.vfs import IVFSView
+from zope.publisher.interfaces.vfs import IVFSCredentials
+
+
+
+class VFSRequest(BaseRequest):
+
+ __implements__ = BaseRequest.__implements__, IVFSCredentials
+
+ # _presentation_type is overridden from the BaseRequest
+ # to implement IVFSView
+ _presentation_type = IVFSView
+
+
+ def __init__(self, body_instream, outstream, environ, response=None):
+ """ """
+ super(VFSRequest, self).__init__(
+ body_instream, outstream, environ, response)
+
+ self._environ = environ
+ self.method = ''
+ self.__setupPath()
+
+
+ def _createResponse(self, outstream):
+ """Create a specific XML-RPC response object."""
+ return VFSResponse(outstream)
+
+ def _authUserPW(self):
+ 'See IVFSCredentials'
+ # XXX This is wrong. Instead of _authUserPW() there should
+ # be a method of all requests called getCredentials() which
+ # returns an ICredentials instance.
+ credentials = self._environ['credentials']
+ return credentials.getUserName(), credentials.getPassword()
+
+ def unauthorized(self, challenge):
+ 'See IVFSCredentials'
+ pass
+
+ def processInputs(self):
+ 'See IPublisherRequest'
+
+ if 'command' in self._environ:
+ self.method = self._environ['command']
+
+ def __setupPath(self):
+ self._setupPath_helper("path")
+
+ def __repr__(self):
+ # Returns a *short* string.
+ return '<%s instance at 0x%x, path=%s>' % (
+ str(self.__class__), id(self), '/'.join(self._traversal_stack))
+
+
+"""VFS-View for IFile
+
+VFS-view implementation for a generic file.
+
+$Id$
+"""
+import datetime
+zerotime = datetime.datetime.fromtimestamp(0)
+
+from zope.component import queryAdapter
+
+
+from zope.publisher.interfaces.vfs import IVFSFilePublisher
+
+from zope.app.interfaces.dublincore import IZopeDublinCore
+
+
+class VFSFileView(VFSView):
+ """Abstract class providing the infrastructure for a basic VFS view
+ for basic file-like content object."""
+
+ __implements__ = IVFSFilePublisher, VFSView.__implements__
+
+
+ # These methods need to be implmented by the real view class
+
+ def _setData(self, data):
+ raise NotImplemented
+
+ def _getData(self):
+ raise NotImplemented
+
+ def _getSize(self):
+ return len(self._getData())
+
+ def read(self, mode, outstream, start = 0, end = -1):
+ """See IVFSFilePublisher"""
+
+ data = self._getData()
+ try:
+ if end != -1: data = data[:end]
+ if start != 0: data = data[start:]
+ except TypeError:
+ pass
+ outstream.write(data)
+
+
+ def write(self, mode, instream, start = 0):
+ """See IVFSFilePublisher"""
+ try:
+ instream.seek(start)
+ except:
+ pass
+ self._setData(instream.read())
+
+
+ def check_writable(self, mode):
+ """See IVFSFilePublisher"""
+ return 1
+
+ def isdir(self):
+ """See IVFSObjectPublisher"""
+ return 0
+
+
+ def isfile(self):
+ """See IVFSObjectPublisher"""
+ return 1
+
+
+ def stat(self):
+ """See IVFSObjectPublisher"""
+ dc = queryAdapter(self, IZopeDublinCore)
+ if dc is not None:
+ modified = dc.modified
+ created = dc.created
+ else:
+ created = zerotime
+ modified = zerotime
+
+ if created is None:
+ created = zerotime
+
+ if modified is None:
+ modified = created
+
+ size = self._getSize()
+ uid = "nouser"
+ gid = "nogroup"
+ return (504, 0, 0, 0, uid, gid, size, modified, modified, created)
+
+
+ def publishTraverse(self, request, name):
+ """See IVFSPublisher"""
+ # Traversing always stops here.
+ return None
=== Zope3/src/zope/publisher/xmlrpc.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:49 2002
+++ Zope3/src/zope/publisher/xmlrpc.py Wed Dec 25 09:15:18 2002
@@ -0,0 +1,199 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope.publisher.interfaces.xmlrpc import IXMLRPCPublisher
+from zope.publisher.http import DefaultPublisher
+
+class MethodPublisher(DefaultPublisher):
+ """Simple XML-RPC publisher that is identical to the HTTP Default Publisher
+ except that it implements the IXMLRPCPublisher interface.
+ """
+
+ __implements__ = IXMLRPCPublisher
+
+
+"""
+
+$Id$
+"""
+
+import xmlrpclib
+from cgi import FieldStorage
+from zope.publisher.http import HTTPRequest
+from zope.publisher.interfaces.xmlrpc import IXMLRPCPublisher
+from zope.publisher.interfaces.xmlrpc import IXMLRPCPublication
+from zope.publisher.interfaces.xmlrpc import IXMLRPCPresentation
+
+
+
+class XMLRPCRequest(HTTPRequest):
+
+ __implements__ = HTTPRequest.__implements__, IXMLRPCPublication
+
+ # _presentation_type is overridden from the BaseRequest
+ # to implement IXMLRPCPublisher
+ _presentation_type = IXMLRPCPresentation
+
+
+ _args = ()
+
+
+ def _createResponse(self, outstream):
+ """Create a specific XML-RPC response object."""
+ return XMLRPCResponse(outstream)
+
+
+ def processInputs(self):
+ 'See IPublisherRequest'
+
+ # Parse the request XML structure
+ self._args, function = xmlrpclib.loads(self._body_instream.read())
+ # Translate '.' to '/' in function to represent object traversal.
+ function = function.replace('.', '/')
+
+ if function:
+ self.setPathSuffix((function,))
+
+
+class TestRequest(XMLRPCRequest):
+
+ def __init__(self, body_instream=None, outstream=None, environ=None,
+ response=None, **kw):
+
+ _testEnv = {
+ 'SERVER_URL': 'http://127.0.0.1',
+ 'HTTP_HOST': '127.0.0.1',
+ 'CONTENT_LENGTH': '0',
+ 'GATEWAY_INTERFACE': 'TestFooInterface/1.0',
+ }
+
+ if environ:
+ _testEnv.update(environ)
+ if kw:
+ _testEnv.update(kw)
+ if body_instream is None:
+ from StringIO import StringIO
+ body_instream = StringIO('')
+
+ if outstream is None:
+ outstream = StringIO()
+
+ super(TestRequest, self).__init__(
+ body_instream, outstream, _testEnv, response)
+
+
+
+"""
+
+$Id$
+"""
+import xmlrpclib
+
+from zope.publisher.http import HTTPResponse
+from zope.proxy.introspection import removeAllProxies
+
+
+class XMLRPCResponse(HTTPResponse):
+ """XMLRPC response
+ """
+
+ __implements__ = HTTPResponse.__implements__
+
+
+ def setBody(self, body):
+ """Sets the body of the response
+
+ Sets the return body equal to the (string) argument "body". Also
+ updates the "content-length" return header.
+
+ If the body is a 2-element tuple, then it will be treated
+ as (title,body)
+
+ If is_error is true then the HTML will be formatted as a Zope error
+ message instead of a generic HTML page.
+ """
+ body = removeAllProxies(body)
+ if isinstance(body, xmlrpclib.Fault):
+ # Convert Fault object to XML-RPC response.
+ body = xmlrpclib.dumps(body, methodresponse=1)
+ else:
+ # Marshall our body as an XML-RPC response. Strings will be sent
+ # strings, integers as integers, etc. We do *not* convert
+ # everything to a string first.
+ if body is None:
+ body = xmlrpclib.False # Argh, XML-RPC doesn't handle null
+ try:
+ body = xmlrpclib.dumps((body,), methodresponse=1)
+ except Exception, e:
+ self.handleException(e)
+ return
+ # Set our body to the XML-RPC message, and fix our MIME type.
+ self.setHeader('content-type', 'text/xml')
+
+ self._body = body
+ self._updateContentLength()
+
+ if not self._status_set:
+ self.setStatus(200)
+
+
+ def handleException(self, exc_info):
+ """Handle Errors during publsihing and wrap it in XML-RPC XML"""
+ t, value = exc_info[:2]
+
+ import traceback
+ traceback.print_tb(exc_info[2])
+ print t
+ print value
+
+ # Create an appropriate Fault object. Unfortunately, we throw away
+ # most of the debugging information. More useful error reporting is
+ # left as an exercise for the reader.
+ Fault = xmlrpclib.Fault
+ fault_text = None
+ try:
+ if isinstance(value, Fault):
+ fault_text = value
+ elif isinstance(value, Exception):
+ fault_text = Fault(-1, "Unexpected Zope exception: " +
+ str(value))
+ else:
+ fault_text = Fault(-2, "Unexpected Zope error value: " +
+ str(value))
+ except:
+ fault_text = Fault(-3, "Unknown Zope fault type")
+
+ # Do the damage.
+ self.setBody(fault_text)
+ self.setStatus(200)
+
+
+"""
+
+$Id$
+"""
+__metaclass__ = type # All classes are new style when run with Python 2.2+
+
+from zope.publisher.interfaces.xmlrpc import IXMLRPCView
+
+class XMLRPCView:
+
+ __implements__ = IXMLRPCView
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request