[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