[Zope3-checkins] SVN: Zope3/branches/jim-bobo/src/zope/bobo/ Checking in initial work.

Jim Fulton jim at zope.com
Sat Dec 4 14:04:40 EST 2004


Log message for revision 28563:
  Checking in initial work.
  
  I'm currently stuck on how to deal with Python-defined adapters.
  When I didn this work a couple of weeks ago, I thought I would 
  need some technology that I planned to build but that would
  take a long time.  Now I'm not so sure...
  
  

Changed:
  A   Zope3/branches/jim-bobo/src/zope/bobo/
  A   Zope3/branches/jim-bobo/src/zope/bobo/README.txt
  A   Zope3/branches/jim-bobo/src/zope/bobo/__init__.py
  A   Zope3/branches/jim-bobo/src/zope/bobo/interfaces.py
  A   Zope3/branches/jim-bobo/src/zope/bobo/publication.py
  A   Zope3/branches/jim-bobo/src/zope/bobo/publication.txt
  A   Zope3/branches/jim-bobo/src/zope/bobo/resource.py
  A   Zope3/branches/jim-bobo/src/zope/bobo/testing.py
  A   Zope3/branches/jim-bobo/src/zope/bobo/tests.py

-=-
Added: Zope3/branches/jim-bobo/src/zope/bobo/README.txt
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/README.txt	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/README.txt	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,109 @@
+Bobo 3, Zope 3 for Idiots
+=========================
+
+The original Bobo provided Python programmers a very easy way to
+create web aplications.  This allowed a lot of people to be productive
+quickly and brought many people to Principia, and later Zope.
+
+Zope provides important facilities for complex web applications. Zope
+3 provides new and better ways to manage complex applications.
+Unfortunately, these techniques may make Zope 3 too heavy for simpler
+applications. There's a lot to be learned and remembered.
+
+The later point is particularly interesting.  If a programmer uses
+Zope occassionally, it should be easy for them to remember basic ideas
+so that they don't have to relearn Zope each time they come back to
+it.
+
+Zope is an application server.  One way it supports applications is by
+providing folders that can manage content objects. These content
+applications can, themselves, be folders. For example, to support a
+blog, a wiki, and a bug tracker, one can simply drop blog, wiki, and
+tracker objects into a root folder.  To get this however, one must use
+the "folder" application and must use the web interface to create the
+individual application objects, which must, in turn, live in the ZODB
+and follow the rules of persistence.
+
+Some developers want to just write python modules. They don't want to
+be forced to use the ZODB or to use the traditional through-the-web
+interface of Zope 3. Forcing them to do so violated the "don't make
+people do anything" goal of Zope 3.
+
+Zope 3 aspires to be a cloud of components that people can use to
+build a variety of applications.  Providing alternative application
+models could help to test and refine that mission.
+
+What is *essential* to Zope 3:
+
+- Object publishing, including traversal
+
+- Component Architecture
+
+- Explicit security
+
+- Configuration separate from code
+
+  Does it matter if this is Python? ZCML? ZConfig?
+
+What is *not* essential to Zope 3:
+
+- Persistence or ZODB
+
+- Acquisition
+
+- Folders
+
+- Extensibility
+
+We'll develop a series of examples below that show how to write
+applications with Bobo. Each example will introduce a little more
+functionality, and a little more complexity.
+
+Hello world
+===========
+
+We'll start with the classic simplest application.  We'll write an
+application that simply supplied a greeting:
+
+    >>> from zope import bobo
+
+    >>> class Hello(bobo.PublicResource):
+    ...
+    ...     def __call__(self):
+    ...         return 'Hello world!'
+
+Bobo applications are objects that provide some sort of web
+interface.  To provide a web interface, objects must implement certain
+APIs that support URL traversal and that indicate an explicit intent
+to provide the web interfaces. Bobo won't publish objects that don't
+provide publishing support.  The `PublicResource` class provides these
+APIs.
+
+Bobo also uses the `zope.security` package to protect objects from
+unauthorized access.  The `PublicResource` class provides security
+declarations that allow us to ignore security issues for applications
+that are meant to be public.
+
+Before discussing how to publish the application, we'll use Bobo's
+testing server to test our application class. The testing server
+simulates the publication process. It lets us try things out without
+having to create a network server.
+
+    >>> from zope.bobo.testing import Server
+    >>> server = Server(Hello)
+
+    >>> print server.request('GET / HTTP/1.1')
+    HTTP/1.1 200 Ok
+    Content-Length: 12
+    Content-Type: text/plain;charset=utf-8
+    <BLANKLINE>
+    Hello world!
+
+We initialized the server with our application class.  To test it, we
+simply called the `request` method with a request string. Printing the
+result displays an HTTP response message.
+
+Note that the output is utf-8 encoded.  Bobo applications should
+normally be written using unicode strings.  The publisher
+automatically encodes output using the utf-8 text encoding. It also
+decodes input using utf-8.


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/__init__.py
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/__init__.py	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/__init__.py	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,19 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Bobo web-application kit
+
+$Id$
+"""
+
+from zope.bobo.resource import PublicResource


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/interfaces.py
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/interfaces.py	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/interfaces.py	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,66 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Bobo interfaces
+
+$Id$
+"""
+
+import zope.interface
+
+class IPublicationEvent(zope.interface.Interface):
+
+    request = zope.interface.Attribute('Request being published')
+
+class IObjectPublicationEvent(IPublicationEvent):
+
+    object = zope.interface.Attribute('Object being published')
+
+class PublicationEvent(object):
+
+    zope.interface.implements(IPublicationEvent)
+
+    def __init__(self, request):
+        self.request = request
+
+class ObjectPublicationEvent(object):
+
+    zope.interface.implements(IObjectPublicationEvent)
+
+    def __init__(self, request, object):
+        self.request = request
+        self.object = object
+
+class BeginRequest(PublicationEvent):
+    "A request started"
+
+class BeforeTraverse(ObjectPublicationEvent):
+    "We are about to traverse an object"
+
+class AfterTraversal(ObjectPublicationEvent):
+    "We are finished with object traversal"
+
+class AfterCall(ObjectPublicationEvent):
+    "We called an objevt without error"
+
+class PublicationException(object):
+    "A call failed"
+
+    def __init__(self, request, object, exc_info, retry_allowed):
+        self.request = request
+        self.object = object
+        self.exc_info = exc_info
+        self.retry_allowed = retry_allowed
+
+class EndRequest(ObjectPublicationEvent):
+    "A request ended"


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/publication.py
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/publication.py	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/publication.py	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Bobo Publication
+
+$Id$
+"""
+
+from zope.event import notify
+import zope.interface
+
+from zope.bobo.interfaces import BeginRequest
+from zope.bobo.interfaces import BeforeTraverse
+from zope.bobo.interfaces import AfterTraversal
+from zope.bobo.interfaces import AfterCall
+from zope.bobo.interfaces import PublicationException
+from zope.bobo.interfaces import EndRequest
+
+from zope.component import queryMultiAdapter
+from zope.publisher.interfaces import IPublishTraverse
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.interfaces.browser import IBrowserPublication
+from zope.publisher.publish import mapply
+from zope.security.checker import ProxyFactory
+
+class Publication(object):
+
+    zope.interface.implements(IBrowserPublication)
+
+    def __init__(self, resource_factory):
+        self.resource_factory = resource_factory
+    
+    def beforeTraversal(self, request):
+        notify(BeginRequest(request))
+
+    def getApplication(self, request):
+        return ProxyFactory(self.resource_factory(request))
+
+    def callTraversalHooks(self, request, ob):
+        notify(BeforeTraverse(request, ob))
+        
+
+    def traverseName(self, request, ob, name):
+        if not IPublishTraverse.providedBy(ob):
+            ob = queryMultiAdapter((ob, request), IPublishTraverse)
+            if ob is None:
+                raise NotFound(ob, name, request)
+        ob = ob.publishTraverse(request, name)
+
+        ob = ProxyFactory(ob)
+
+        return ob
+
+    def getDefaultTraversal(self, request, ob):
+        if IBrowserPublisher.providedBy(ob):
+            # ob is already proxied, so the result of calling a method will be
+            return ob.browserDefault(request)
+        else:
+            adapter = queryMultiAdapter((ob, request), IBrowserPublisher)
+            if adapter is None:
+                # We don't allow publication of non browser publishers
+                raise NotFound(ob, '', request)
+            ob, path = adapter.browserDefault(request)
+            ob = ProxyFactory(ob)
+            return ob, path
+        
+    def afterTraversal(self, request, ob):
+        notify(AfterTraversal(request, ob))
+
+    def callObject(self, request, ob):
+        return mapply(ob, request.getPositionalArguments(), request)
+
+    def afterCall(self, request, ob):
+        notify(AfterCall(request, ob))
+
+    def handleException(self, ob, request, exc_info, retry_allowed=1):
+        raise exc_info[0], exc_info[1], exc_info[2]
+        notify(PublicationException(request, ob, exc_info, retry_allowed))
+
+    def endRequest(self, request, ob):
+        """Do any end-of-request cleanup
+        """
+        notify(EndRequest(request, ob))
+            


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/publication.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/publication.txt
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/publication.txt	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/publication.txt	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,123 @@
+Bobo Publication
+================
+
+Publication objects are used to customize the Zope publisher for a
+particular application.  Publications provide a number of hooks the
+publisher while publishing requests.  The Bobo publisher simply
+delegates most of these to event subscribers:
+
+    >>> events = []
+    >>> import zope.event
+    >>> zope.event.subscribers.append(events.append)
+
+    >>> import zope.bobo.publication
+    >>> pub = zope.bobo.publication.Publication(None)
+
+    >>> pub.beforeTraversal('req')
+    >>> len(events), events[0].__class__, events[0].request
+    (1, <class 'zope.bobo.interfaces.BeginRequest'>, 'req')
+    >>> del events[0]
+
+    >>> pub.callTraversalHooks('req', 'ob')
+    >>> len(events), events[0].__class__, events[0].request, events[0].object
+    (1, <class 'zope.bobo.interfaces.BeforeTraverse'>, 'req', 'ob')
+    >>> del events[0]
+
+    >>> pub.afterTraversal('req', 'ob')
+    >>> len(events), events[0].__class__, events[0].request, events[0].object
+    (1, <class 'zope.bobo.interfaces.AfterTraversal'>, 'req', 'ob')
+    >>> del events[0]
+
+    >>> pub.afterCall('req', 'ob')
+    >>> len(events), events[0].__class__, events[0].request, events[0].object
+    (1, <class 'zope.bobo.interfaces.AfterCall'>, 'req', 'ob')
+    >>> del events[0]
+
+    >>> pub.endRequest('req', 'ob')
+    >>> len(events), events[0].__class__, events[0].request, events[0].object
+    (1, <class 'zope.bobo.interfaces.EndRequest'>, 'req', 'ob')
+    >>> del events[0]
+
+    >>> zope.event.subscribers.remove(events.append)
+
+There are 4 calls for which the publication provides special
+behavior.  Before discussing these, we need to discuss the
+`IBrowserPublisher` interface.  The publication works with objects
+that provide `IBrowserPublisher` to support URL traversal. In
+addition, the publisher is responsible for selecting the initial oject
+to be traversed.  When the publisher is created, we need to supply it
+with a factory for computing that initial object.
+
+To see how this works, we'll create a class that creates browser
+publishers:
+
+    >>> import zope.interface
+    >>> import zope.publisher.interfaces
+    >>> from zope.publisher.interfaces.browser import IBrowserPublisher
+
+    >>> class R:
+    ...     zope.interface.implements(IBrowserPublisher)
+    ...
+    ...     def __init__(self, request):
+    ...         self.request = request
+    ...
+    ...     def publishTraverse(self, request, name):
+    ...         if name == 'zope':
+    ...             return R(42)
+    ...         raise zope.publisher.interfaces.NotFound(self, name)
+    ... 
+    ...     def browserDefault(self, request):
+    ...         return self, ('eek')
+
+Now, we'll create a publication, providing this class as an argument:
+
+    >>> pub = zope.bobo.publication.Publication(R)
+
+When we call the `getApplication` method, we'll get an instance of `R`:
+
+    >>> app = pub.getApplication('req')
+
+The returned object is security proxied:
+
+    >>> app.request # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    ForbiddenAttribute: ('request', ...
+
+    >>> from zope.security.proxy import removeSecurityProxy
+    >>> app = removeSecurityProxy(app)
+    >>> app.__class__.__name__, app.request
+    ('R', 'req')
+
+It isn't necessary that the factory return an `IBrowserPublisher`. Any
+factory taking a single argument will do.  The object returned should,
+at least, be adaptable, with a request, to IBrowserPublisher.
+
+Publication objects are responsible for traversal.  If passed an
+`IBrowserPublisher` (or an `IPublishTraverse`), the publication simply
+calls it's `publishTraverse` method:
+
+    >>> r = pub.traverseName('req', app, 'zope')
+
+The result is security proxied:
+
+    >>> r.request # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    ForbiddenAttribute: ('request', ...
+
+    >>> r = removeSecurityProxy(r)
+    >>> r.__class__.__name__, r.request
+    ('R', 42)
+ 
+We can also traverse objects that don't provide `IBrowserPublisher`,
+but they must have an adapter to `IBrowserPublisher`:
+
+    >>> class A:
+    ...     zope.interface.implements(IBrowserPublisher)
+    ...     zope.component.adapts(Interface, Interface) # adapt anything
+    ...
+    ...     def __init__(self, context, request):
+    ...         self.context = context
+    ...         self.request = request
+


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/publication.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/resource.py
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/resource.py	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/resource.py	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Resource base class
+
+$Id$
+"""
+
+import zope.interface
+from zope.publisher.interfaces.browser import IBrowserPublisher
+import zope.security.checker
+
+class PublicResource(object):
+
+    __Security_checker__ = zope.security.checker.NamesChecker(
+        ('publishTraverse', 'browserDefault', '__call__'))
+
+    zope.interface.implements(IBrowserPublisher)
+
+    def __init__(self, request):
+        self.request = request
+
+    def publishTraverse(self, request, name):
+        ob = getattr(self, name, None)
+        if not IBrowserPublisher.providedBy(ob):
+            raise zope.publisher.interfaces.NotFound(self, name)
+
+    def browserDefault(self, request):
+        return self, ()
+
+    


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/resource.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/testing.py
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/testing.py	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/testing.py	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,158 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Bobo testing support
+
+$Id$
+"""
+
+from StringIO import StringIO
+import rfc822
+import urllib
+
+from zope.publisher.browser import TestRequest
+from zope.publisher.publish import publish
+
+from zope.bobo.publication import Publication
+
+class Server:
+
+    def __init__(self, resource_factory):
+        self.publication = Publication(resource_factory)
+
+    def request(self, request_string, handle_errors=True):
+        """Execute an HTTP request string via the publisher
+
+        This is used for HTTP doc tests.
+        """
+
+        # Discard leading white space to make call layout simpler
+        request_string = request_string.lstrip()
+
+        # split off and parse the command line
+        l = request_string.find('\n')
+        if l < 0:
+            l = len(request_string)
+        command_line = request_string[:l].rstrip()
+        request_string = request_string[l+1:]
+        method, path, protocol = command_line.split()
+        path = urllib.unquote(path)
+
+        instream = StringIO(request_string)
+        environment = {"HTTP_HOST": 'localhost',
+                       "HTTP_REFERER": 'localhost',
+                       "REQUEST_METHOD": method,
+                       "SERVER_PROTOCOL": protocol,
+                       }
+
+        headers = [split_header(header)
+                   for header in rfc822.Message(instream).headers]
+        for name, value in headers:
+            name = ('_'.join(name.upper().split('-')))
+            if name not in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
+                name = 'HTTP_' + name
+            environment[name] = value.rstrip()
+
+        auth_key = 'HTTP_AUTHORIZATION'
+        if environment.has_key(auth_key):
+            environment[auth_key] = auth_header(environment[auth_key])
+
+        outstream = StringIO()
+
+        header_output = HTTPHeaderOutput(
+            protocol, ('x-content-type-warning', 'x-powered-by'))
+
+        request = self._request(path, environment, instream, outstream)
+        request.response.setHeaderOutput(header_output)
+        response = DocResponseWrapper(request.response, outstream,
+                                      header_output)
+
+        publish(request, handle_errors=handle_errors)
+
+        return response
+    
+    def _request(self, path, environment, stdin, stdout):
+        """Create a request
+        """
+
+        env = {}
+        p=path.split('?')
+        if len(p)==1:
+            env['PATH_INFO'] = p[0]
+        elif len(p)==2:
+            env['PATH_INFO'], env['QUERY_STRING'] = p
+        else:
+            raise ValueError("Too many ?s in path", path)
+        env.update(environment)
+
+        request = TestRequest(stdin, stdout, env)
+        request.setPublication(self.publication)
+
+        return request
+
+
+
+class HTTPHeaderOutput:
+
+    def __init__(self, protocol, omit):
+        self.headers = {}
+        self.headersl = []
+        self.protocol = protocol
+        self.omit = omit
+    
+    def setResponseStatus(self, status, reason):
+        self.status, self.reason = status, reason
+
+    def setResponseHeaders(self, mapping):
+        self.headers.update(dict(
+            [('-'.join([s.capitalize() for s in name.split('-')]), v)
+             for name, v in mapping.items()
+             if name.lower() not in self.omit]
+        ))
+
+    def appendResponseHeaders(self, lst):
+        headers = [split_header(header) for header in lst]
+        self.headersl.extend(
+            [('-'.join([s.capitalize() for s in name.split('-')]), v)
+             for name, v in headers
+             if name.lower() not in self.omit]
+        )
+
+    def __str__(self):
+        out = ["%s: %s" % header for header in self.headers.items()]
+        out.extend(["%s: %s" % header for header in self.headersl])
+        out.sort()
+        out.insert(0, "%s %s %s" % (self.protocol, self.status, self.reason))
+        return '\n'.join(out)
+
+class DocResponseWrapper:
+    """Response Wrapper for use in doc tests
+    """
+
+    def __init__(self, response, outstream, header_output):
+        self._response = response
+        self._outstream = outstream
+        self.header_output = header_output
+
+    def getBody(self):
+        """Returns the full HTTP output (headers + body)"""
+        return self._outstream.getvalue()
+
+    def __str__(self):
+        body = self.getBody()
+        if body:
+            return "%s\n\n%s" % (self.header_output, body)
+        return "%s\n" % (self.header_output)
+
+    def __getattr__(self, attr):
+        return getattr(self._response, attr)


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/testing.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/jim-bobo/src/zope/bobo/tests.py
===================================================================
--- Zope3/branches/jim-bobo/src/zope/bobo/tests.py	2004-12-04 18:41:32 UTC (rev 28562)
+++ Zope3/branches/jim-bobo/src/zope/bobo/tests.py	2004-12-04 19:04:40 UTC (rev 28563)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2004 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 unittest
+
+def test_suite():
+    from zope.testing import doctest
+    
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt', 'publication.txt'),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: Zope3/branches/jim-bobo/src/zope/bobo/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list