[pypy-svn] r39218 - in pypy/branch/guido-buildtool-web/pypy/tool/build: . web web/templates web/test

guido at codespeak.net guido at codespeak.net
Mon Feb 19 22:02:29 CET 2007


Author: guido
Date: Mon Feb 19 22:02:27 2007
New Revision: 39218

Modified:
   pypy/branch/guido-buildtool-web/pypy/tool/build/build.py
   pypy/branch/guido-buildtool-web/pypy/tool/build/web/app.py
   pypy/branch/guido-buildtool-web/pypy/tool/build/web/server.py
   pypy/branch/guido-buildtool-web/pypy/tool/build/web/templates/buildersinfo.html
   pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_app.py
   pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_server.py
Log:
Added id() method to BuildRequest that returns a consistent, reproducable (but
hopefully still unique enough) ID for that BuildRequest, so the web app can
link to it etc., removed the Resource base class, now the Resources are plain
callables (that must have a .exposed attribute set to True), probably fixed
HEAD support.


Modified: pypy/branch/guido-buildtool-web/pypy/tool/build/build.py
==============================================================================
--- pypy/branch/guido-buildtool-web/pypy/tool/build/build.py	(original)
+++ pypy/branch/guido-buildtool-web/pypy/tool/build/build.py	Mon Feb 19 22:02:27 2007
@@ -147,6 +147,13 @@
 build_end_time: %(build_end_time)s
 """ % self.todict()
 
+    def id(self):
+        # XXX can this be made better? we certainly don't want clashes :|
+        str = '%r\n%r\n%r\n%r\n%r' % (self.email, self.sysinfo,
+                                      self.compileinfo, self.svnurl,
+                                      self.svnrev)
+        return '%s.%s' % (self.request_time, py.std.md5.new(str).hexdigest())
+
     def _fromstring(cls, s):
         data = {}
         for line in s.strip().split('\n'):

Modified: pypy/branch/guido-buildtool-web/pypy/tool/build/web/app.py
==============================================================================
--- pypy/branch/guido-buildtool-web/pypy/tool/build/web/app.py	(original)
+++ pypy/branch/guido-buildtool-web/pypy/tool/build/web/app.py	Mon Feb 19 22:02:27 2007
@@ -7,8 +7,7 @@
 from pypy.tool.build import config
 from pypy.tool.build import execnetconference
 from pypy.tool.build.build import BuildRequest
-from pypy.tool.build.web.server import HTTPError, Resource, Collection, \
-                                       Handler, FsFile
+from pypy.tool.build.web.server import HTTPError, Collection, Handler, FsFile
 
 from templess import templess
 
@@ -19,17 +18,10 @@
             '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n%s' % (
             html.strip().encode('UTF-8'),))
 
-class IndexPage(Resource):
-    """ the index page """
-    def handle(self, handler, path, query):
-        template = templess.template(
-            mypath.join('templates/index.html').read())
-        return ({'Content-Type': 'text/html; charset=UTF-8'},
-                fix_html(template.unicode({})))
-
-class ServerPage(Resource):
+class ServerPage(object):
     """ base class for pages that communicate with the server
     """
+    exposed = True
 
     def __init__(self, config, gateway=None):
         self.config = config
@@ -71,7 +63,7 @@
 class ServerStatusPage(ServerPage):
     """ a page displaying overall meta server statistics """
 
-    def handle(self, handler, path, query):
+    def __call__(self, handler, path, query):
         template = templess.template(
             mypath.join('templates/serverstatus.html').read())
         return ({'Content-Type': 'text/html; charset=UTF-8'},
@@ -81,7 +73,7 @@
         return self.call_method('status')
 
 class BuildersInfoPage(ServerPage):
-    def handle(self, handler, path, query):
+    def __call__(self, handler, path, query):
         template = templess.template(
             mypath.join('templates/buildersinfo.html').read())
         return ({'Content-Type': 'text/html; charset=UTF-8'},
@@ -95,7 +87,11 @@
             binfo['sysinfo'] = [binfo['sysinfo']]
             if binfo['busy_on']:
                 b = binfo['busy_on']
-                d = BuildRequest.fromstring(binfo['busy_on']).todict()
+                req = BuildRequest.fromstring(binfo['busy_on'])
+                id = req.id()
+                d = req.todict()
+                d['id'] = id
+                d['href'] = '/builds/%s' % (id,)
                 d.pop('sysinfo', None) # same as builder
                 d.pop('build_end_time', None) # it's still busy ;)
                 # templess doesn't understand dicts this way...
@@ -115,13 +111,13 @@
         super(BuildPage, self).__init__(config, gateway)
         self._buildid = buildid
 
-    def handle(self, handler, path, query):
+    def __call__(self, handler, path, query):
         pass
 
 class BuildsIndexPage(ServerPage):
     """ display the list of available builds """
 
-    def handle(self, handler, path, query):
+    def __call__(self, handler, path, query):
         template = templess.template(
             mypath.join('templates/builds.html').read())
         return ({'Content-Type': 'text/html; charset=UTF-8'},
@@ -153,11 +149,17 @@
 
 class Application(Collection):
     """ the application root """
-    index = IndexPage()
     style = FsFile(mypath.join('theme/style.css'), 'text/css')
     serverstatus = ServerStatusPage(config)
     buildersinfo = BuildersInfoPage(config)
     builds = Builds(config)
+    
+    def index(self, handler, path, query):
+        template = templess.template(
+            mypath.join('templates/index.html').read())
+        return ({'Content-Type': 'text/html; charset=UTF-8'},
+                fix_html(template.unicode({})))
+    index.exposed = True
 
 class AppHandler(Handler):
     application = Application()

Modified: pypy/branch/guido-buildtool-web/pypy/tool/build/web/server.py
==============================================================================
--- pypy/branch/guido-buildtool-web/pypy/tool/build/web/server.py	(original)
+++ pypy/branch/guido-buildtool-web/pypy/tool/build/web/server.py	Mon Feb 19 22:02:27 2007
@@ -1,3 +1,5 @@
+import sys
+import traceback
 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
 
 # some generic stuff to make working with BaseHTTPServer a bit nicer...
@@ -36,28 +38,18 @@
             data = ' (%s)' % (self.data,)
         return '<HTTPException %s "%s"%s>' % (self.status, self.message, data)
 
-class Resource(object):
-    """ an HTTP resource
-    
-        essentially this is an object with a path that does not end on a slash,
-        and no support for PATH_INFO
-    """
-
-    def handle(self, handler, path, query):
-        """ handle a single request to self 
-        
-            returns a tuple (content_type, data) where data is either a string
-            (non-unicode!) or a file-like object with a read() method that
-            accepts an integer (size) argument
-        """
-        raise NotImplemented('abstract base class')
-
-class Collection(Resource):
+class Collection(object):
     """ an HTTP collection
     
         essentially this is a container object that has a path that ends on a
         slash, and support for PATH_INFO (so can have (virtual or not)
         children)
+
+        children are callable attributes of ourselves that have an 'exposed'
+        attribute themselves, that accept 3 arguments: 'handler', a reference
+        to the BaseHTTPHandler that handles the request (XXX should be
+        abstracted?), 'path', the requested path to the object, and 'query',
+        the (unparsed!) GET query string (without a preceding ?)
     """
 
     def traverse(self, path, orgpath):
@@ -72,7 +64,7 @@
             a lookup on self, if that fails a 404 is raised, if successful
             the item is used to continue traversal (if the object found is
             a Collection type) or to handle the request (if the object found
-            is a Resource type)
+            is a callable with .exposed set to True)
 
             if path equals '', a lookup for 'index' is done
 
@@ -83,7 +75,9 @@
         if name == '':
             name = 'index'
         resource = getattr(self, name, None)
-        if resource is None or not isinstance(resource, Resource):
+        if (resource is None or (not isinstance(resource, Collection) and
+                (not callable(resource) or
+                    not getattr(resource, 'exposed', True)))):
             raise HTTPError(404)
         if path:
             if not isinstance(resource, Collection):
@@ -93,6 +87,9 @@
             if isinstance(resource, Collection):
                 # targeting a collection directly: redirect to its 'index'
                 raise HTTPError(301, orgpath + '/')
+            if not getattr(resource, 'exposed', False):
+                # don't reveal what is not accessible...
+                raise HTTPError(404)
             return resource
 
 class Handler(BaseHTTPRequestHandler):
@@ -107,15 +104,22 @@
         path, query = self.process_path(self.path)
         try:
             resource = self.find_resource(path, query)
-            headers, data = resource.handle(self, path, query)
+            headers, data = resource(self, path, query)
         except HTTPError, e:
             status = e.status
             headers, data = self.process_http_error(e)
+        except:
+            exc, e, tb = sys.exc_info()
+            tb_formatted = '\n'.join(traceback.format_tb(tb))
+            status = 200
+            data = 'An error has occurred: %s - %s\n\n%s' % (exc, e,
+                                                             tb_formatted)
+            headers = {'Content-Type': 'text/plain'}
         else:
             status = 200
             if not 'content-type' in [k.lower() for k in headers]:
                 headers['Content-Type'] = 'text/html; charset=UTF-8'
-        self.response(status, headers, data)
+        self.response(status, headers, data, send_body)
 
     do_POST = do_GET
 
@@ -156,7 +160,7 @@
             body = 'Error: %s (%s)' % (e.status, e.message)
         return headers, body
     
-    def response(self, status, headers, body):
+    def response(self, status, headers, body, send_body=True):
         """ generate the HTTP response and send it to the client
         """
         self.send_response(status)
@@ -166,6 +170,8 @@
         for keyword, value in headers.iteritems():
             self.send_header(keyword, value)
         self.end_headers()
+        if not send_body:
+            return
         if isinstance(body, str):
             self.wfile.write(body)
         elif hasattr(body, 'read'):
@@ -183,15 +189,16 @@
     server = HTTPServer(address, handler)
     server.serve_forever()
 
-# ready-to-use Collection and Resource implementations
-class FsFile(Resource):
+# ready-to-use Collection and resource implementations
+class FsFile(object):
+    exposed = True
     debug = False
     def __init__(self, path, content_type):
         self._path = path
         self._content_type = content_type
 
     _data = None
-    def handle(self, handler, path, query):
+    def __call__(self, handler, path, query):
         if self._data is None or self.debug:
             self._data = self._path.read()
         return ({'Content-Type': self._content_type}, self._data)

Modified: pypy/branch/guido-buildtool-web/pypy/tool/build/web/templates/buildersinfo.html
==============================================================================
--- pypy/branch/guido-buildtool-web/pypy/tool/build/web/templates/buildersinfo.html	(original)
+++ pypy/branch/guido-buildtool-web/pypy/tool/build/web/templates/buildersinfo.html	Mon Feb 19 22:02:27 2007
@@ -30,6 +30,7 @@
             <div class="title">busy on build:</div>
             <div class="value" t:not="busy_on">nothing</div>
             <div class="dict" t:cond="busy_on" t:content="busy_on">
+              <a class="title" t:attr="href href" t:content="id" />
               <div class="pair">
                 <span class="key">request time:</span>
                 <span class="value" t:content="request_time" />

Modified: pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_app.py
==============================================================================
--- pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_app.py	(original)
+++ pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_app.py	Mon Feb 19 22:02:27 2007
@@ -65,9 +65,9 @@
     mod.gateway.exit()
 
 class TestIndexPage(object):
-    def test_handle(self):
-        p = IndexPage()
-        headers, html = p.handle(None, '/', '')
+    def test_call(self):
+        a = Application()
+        headers, html = a.index(None, '/', '')
         assert headers == {'Content-Type': 'text/html; charset=UTF-8'}
         assert html.strip().startswith('<!DOCTYPE html')
         assert html.strip().endswith('</html>')
@@ -80,12 +80,12 @@
         server_channel.send(('set_status', {'foo': 'bar'}))
         assert p.get_status() == {'foo': 'bar'}
 
-    def test_handle(self):
+    def test_call(self):
         server_channel.send(('set_status', {'builders': 3, 'running': 2,
                                             'done': 7, 'waiting': 5,
                                             'queued': 2}))
         p = ServerStatusPage(config, gateway)
-        headers, html = p.handle(None, '/serverstatus', '')
+        headers, html = p(None, '/serverstatus', '')
         assert headers == {'Content-Type': 'text/html; charset=UTF-8'}
         assert html.strip().startswith('<!DOCTYPE html')
         assert html.strip().endswith('</html>')
@@ -99,7 +99,7 @@
                                                    'busy_on': None}]))
         assert p.get_buildersinfo() == [{'sysinfo': ['foo'], 'busy_on': None}]
 
-    def test_handle(self):
+    def test_call(self):
         b = build.BuildRequest('foo at bar.com', {}, {'foo': 'bar'},
                                'http://codespeak.net/svn/pypy/dist', 10, 2,
                                123456789)
@@ -119,23 +119,23 @@
                                                    'busy_on': busy_on,
                                                    }]))
         p = BuildersInfoPage(config, gateway)
-        headers, html = p.handle(None, '/buildersinfo', '')
+        headers, html = p(None, '/buildersinfo', '')
         assert headers == {'Content-Type': 'text/html; charset=UTF-8'}
         assert html.strip().startswith('<!DOCTYPE html')
         assert html.strip().endswith('</html>')
         html_validate(html)
 
 class TestBuildPage(object):
-    def test_handle(self):
+    def test_call(self):
         pass
 
 class TestBuildsIndexPage(object):
     def test_get_builds(self):
         pass
 
-    def test_handle(self):
+    def test_call(self):
         p = BuildsIndexPage(config, gateway)
-        headers, html = p.handle(None, '/builds/', '')
+        headers, html = p(None, '/builds/', '')
         assert headers == {'Content-Type': 'text/html; charset=UTF-8'}
         assert html.strip().startswith('<!DOCTYPE html')
         assert html.strip().endswith('</html>')

Modified: pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_server.py
==============================================================================
--- pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_server.py	(original)
+++ pypy/branch/guido-buildtool-web/pypy/tool/build/web/test/test_server.py	Mon Feb 19 22:02:27 2007
@@ -10,8 +10,10 @@
     def log_request(self, code='-', size='-'):
         pass
 
-class SomePage(Resource):
-    def handle(self, server, path, query):
+class SomePage(object):
+    """ test resource """
+    exposed = True
+    def __call__(self, handler, path, query):
         return ('text/plain', 'foo')
 
 def build_app_structure():
@@ -101,12 +103,12 @@
         py.test.raises(ValueError, "self.handler.response(200, {}, u'xxx')")
 
 class TestFsFile(object):
-    def test_handle(self):
+    def test_call(self):
         temp = py.test.ensuretemp('TestStaticResource.test_handle')
         foo = temp.ensure('foo.txt')
         foo.write('foo')
         r = FsFile(foo, 'text/plain')
-        ret = r.handle(None, '/bar/foo.txt', '')
+        ret = r(None, '/bar/foo.txt', '')
         assert ret[0] == {'Content-Type': 'text/plain'}
         assert ret[1] == 'foo'
         



More information about the Pypy-commit mailing list