[Twisted-web] Batch of nevow bugfixes

James Y Knight twisted-web@twistedmatrix.com
Thu, 8 Jan 2004 18:13:31 -0500


--Apple-Mail-4-807131218
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=US-ASCII;
	format=flowed

So I've got a bunch of nevow patches here. They're all combined in one 
patchfile cause I'm way too lazy to split them up, but it should be 
pretty easy to see what's what.
1) most files: Compatibility with python2.2 (agian). Omitted changes to 
components.wsv, so it shouldn't hurt python2.3 users but won't actually 
work in python2.2 yet.
2) appserver.py: change to renderHTTP that allows returning a 
NOT_DONE_YET from a deferred without blowing up. Probably not necessary 
but makes it easier for me. :)
3) context.py, stan.py: stripContexts and clone look within sublists 
and attributes. This corrects bug #49, which causes the sequence 
renderer to fail to render some parts.
4) context.py, stan.py: use Unset instead of None for specials that 
haven't been set, so that None is a valid value (important at least for 
data attribute!)
5) renderer.py: Fix bug #36 "Nevow has issues with nested deferred 
renderers."
6) simple.py: Make this example work.
7) stan.py: specials/specialMatches: don't find specials on the current 
tag, only children. Fixes bug #50.

All the ones I submitted bug reports on have testcases on the bugreport.


--Apple-Mail-4-807131218
Content-Transfer-Encoding: 7bit
Content-Type: application/octet-stream;
	x-unix-mode=0644;
	name="nevow.patch"
Content-Disposition: attachment;
	filename=nevow.patch

Index: appserver.py
===================================================================
RCS file: /cvs/Quotient/nevow/appserver.py,v
retrieving revision 1.12
diff -u -r1.12 appserver.py
--- appserver.py	8 Jan 2004 01:11:33 -0000	1.12
+++ appserver.py	8 Jan 2004 22:11:57 -0000
@@ -3,6 +3,8 @@
 import cgi
 from copy import copy
 from urllib import unquote
+from types import StringTypes
+
 
 from twisted.web import server
 from twisted.web import resource
@@ -135,7 +137,7 @@
         self.deferred.callback("")
 
     def _cbFinishRender(self, html):
-        if isinstance(html, basestring):
+        if isinstance(html, StringTypes):
             self.write(html)
         server.Request.finish(self)
         return html
@@ -170,10 +172,15 @@
         request.postpath.insert(0, request.prepath.pop())
         return res, segments[1:]
 
-    def renderHTTP(self, request):
-        result = self.original.render(request)
-        if result == server.NOT_DONE_YET:
+    def _handle_NOT_DONE_YET(self, data, request):
+        if data == server.NOT_DONE_YET:
             return request.deferred
+        else:
+            return data
+        
+    def renderHTTP(self, request):
+        result = defer.maybeDeferred(self.original.render, request).addCallback(
+            self._handle_NOT_DONE_YET, request)
         return result
 
 
Index: context.py
===================================================================
RCS file: /cvs/Quotient/nevow/context.py,v
retrieving revision 1.14
diff -u -r1.14 context.py
--- context.py	11 Dec 2003 21:34:27 -0000	1.14
+++ context.py	8 Jan 2004 22:11:57 -0000
@@ -3,13 +3,15 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
+from __future__ import generators
+
 import warnings
 
 from twisted.python import components
 from twisted.python.reflect import qual
 
 from nevow.iwoven import IData
-from nevow.stan import specialMatches, specials, Tag
+from nevow.stan import specialMatches, specials, Tag, Unset
 from nevow.tags import invisible
 
 class _Marker(object):
@@ -36,14 +38,22 @@
         return "More than one %r with the name %r was found." % tuple(self.args[:2])
 
 
-def stripContexts(tag):
-    for i in range(len(tag.children)):
-        obj = tag.children[i]
+def _stripContexts(obj):
+    if isinstance(obj, (list, tuple)):
+        obj = [_stripContexts(x) for x in obj]
+    else:
         if isinstance(obj, WovenContext):
-            tag.children[i] = obj.tag
             obj = obj.tag
         if isinstance(obj, Tag):
-            stripContexts(obj)
+            return stripContexts(obj)
+    return obj
+
+def stripContexts(tag):
+    for i in range(len(tag.children)):
+        tag.children[i] = _stripContexts(tag.children[i])
+    for key in tag.attributes:
+        tag.attributes[key] = _stripContexts(tag.attributes[key])
+    return tag
 
 
 class WovenContext(object):
@@ -54,7 +64,7 @@
     def __init__(self, parent=None, tag=None, precompile=False, remembrances=None, key=None):
         self.tag = tag
         self.parent = parent
-        if key is not None:
+        if key is not None and key is not Unset:
             if self.parent is not None and self.parent.key is not None:
                 self.key = '.'.join((self.parent.key, key))
             else:
@@ -107,9 +117,9 @@
 #        data=None, renderer=None, observer=None, remembrances=None
 
         new = WovenContext(self, tag, self.precompile, key=tag.key)
-        if tag.data is not None:
+        if tag.data is not Unset:
             new.remember(tag.data, IData)
-        if tag.remember is not None:
+        if tag.remember is not Unset:
             new.remember(tag.remember)
 #        if renderer is not None:
 #            # push a renderer onto the stack
@@ -195,7 +205,7 @@
             for x in specialMatches(tag, 'pattern', pattern):
                 keeplooking = True
                 cloned = x.clone()
-                cloned.pattern = None
+                cloned.pattern = Unset
                 yield cloned
         if raiseIfMissing:
             raise RuntimeError, "Pattern %s was not found." % pattern
Index: formless.py
===================================================================
RCS file: /cvs/Quotient/nevow/formless.py,v
retrieving revision 1.32
diff -u -r1.32 formless.py
--- formless.py	4 Jan 2004 15:31:21 -0000	1.32
+++ formless.py	8 Jan 2004 22:11:57 -0000
@@ -20,10 +20,14 @@
 from twisted.internet import reactor, defer
 
 
-import itertools
+class count(object):
+    def __init__(self):
+        self.id = 0
+    def next(self):
+        self.id += 1
+        return self.id
 
-
-nextId = itertools.count().next
+nextId = count().next
 
 
 class InputError(Exception):
@@ -566,7 +570,8 @@
         """
         copied = copy.deepcopy(self.typedValue.arguments)
         curid = 0
-        for arg in copied[::-1]:
+        for n in xrange(len(copied)-1, -1, -1):
+            arg=copied[n]
             curid = arg.id
             if isinstance(arg, Button):
                 break
Index: freeform.py
===================================================================
RCS file: /cvs/Quotient/nevow/freeform.py,v
retrieving revision 1.85
diff -u -r1.85 freeform.py
--- freeform.py	4 Jan 2004 15:31:21 -0000	1.85
+++ freeform.py	8 Jan 2004 22:11:57 -0000
@@ -196,6 +196,15 @@
 
         defaults = IFormDefaults(request)
         value = defaults.getDefault(context.key, context)
+
+        if isinstance(value, Deferred):
+            value.addCallback(self._cb_call, context, data)
+            return value
+        else:
+            return self._cb_call(value, context, data)
+
+    def _cb_call(self, value, context, data):
+        request = context.locate(iwoven.IRequest)
         try:
             _, fbn = calculateFullBindingName(context, context.locate(formless.IBinding))
         except KeyError:
Index: renderer.py
===================================================================
RCS file: /cvs/Quotient/nevow/renderer.py,v
retrieving revision 1.29
diff -u -r1.29 renderer.py
--- renderer.py	23 Dec 2003 06:15:38 -0000	1.29
+++ renderer.py	8 Jan 2004 22:11:57 -0000
@@ -4,6 +4,8 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
+from __future__ import generators
+
 import os
 import stat
 import time
@@ -67,8 +69,12 @@
             buf(item)
         elif isinstance(item, Deferred):
             yield item
-            for flat in _strflatten(context, ISerializable(item.result).serialize(context, None), buf):
-                pass
+            if not hasattr(item, 'result'):
+                print "ERROR: Deferred has no result!"
+                buf("ERROR: Deferred has no result!")
+            else:
+                for sub in _strflatten(context, ISerializable(item.result).serialize(context, None), buf):
+                    yield sub
         else:
             for sub in _strflatten(context, item, buf):
                 yield sub
@@ -76,6 +82,12 @@
 from cStringIO import StringIO
 from twisted.python.context import get as getCtx
 
+def _error(failure):
+    # FIXME: this should *definitely* do something better
+    # doing this leaves the connection hanging, but it's better
+    # than nothing.
+    print "FAILURE !",failure 
+    
 def _subrender(L, itr, context, writer, closer):
     for item in itr:
         if writer is not None:
@@ -88,6 +100,7 @@
             item.addCallback(lambda ignored: transact(_subrender, L, itr, context, writer, closer))
         else:
             item.addCallback(lambda ignored: _subrender(L, itr, context, writer, closer))
+        item.addErrback(_error)
         return item
     data = ''.join(L)
     if writer is not None:
Index: simple.py
===================================================================
RCS file: /cvs/Quotient/nevow/simple.py,v
retrieving revision 1.3
diff -u -r1.3 simple.py
--- simple.py	28 Oct 2003 23:41:16 -0000	1.3
+++ simple.py	8 Jan 2004 22:11:57 -0000
@@ -4,7 +4,11 @@
 # Public License as published by the Free Software Foundation.
 
 
+from twisted.application import service, internet
+from twisted.web import server
+from nevow import renderer
 from nevow.tags import *
+from nevow import stan
 
 import random
 
@@ -17,23 +21,6 @@
     return data * 5
 
 
-def sequence(context, data):
-    patterns = context.generatePatterns('item')
-    return [patterns.next()(data=item) for item in data]
-
-
-def slotFiller(context, data):
-    assert hasattr(data, 'items'), "slotFiller needs an object which returns key, value tuples in response to items."
-    for k, v in data.items():
-        try:
-            slot = context.locateSlots(k).next()
-        except StopIteration:
-            raise RuntimeError, "Slot %s was not found." % k
-        slot.children.append(v)
-    context.tag.renderer = None
-    return context.tag
-
-
 def selectOptioner(context, data):
     tag = context.tag.clone(deep=False)
     tag(name="flavor")
@@ -46,8 +33,8 @@
     tag.renderer = None
     return tag
 
-
-doc = html[
+class Simple(renderer.Renderer):
+  document = html[
     head[
         title["Hello, World"]
     ],
@@ -59,7 +46,7 @@
             "Here are some random numbers: ",
             str, " ",
             str, " ",
-            str, " ",
+            str, " "
         ],
         ul(data={'one': 1, 'two': rand, 'three': 3})[
             li(data=directive("one"), renderer=str),
@@ -70,7 +57,7 @@
             li(data=_2, renderer=multiply),
             li(data=_1, renderer=multiply),
             li(data=_0, renderer=multiply),
-            li(data=multiply, renderer=sequence)[
+            li(data=multiply, renderer=stan.sequence)[
                 div(style="border: 1px solid blue; margin-bottom: 0.5em", pattern="item", renderer=str),
                 div(style="border: 1px solid red; margin-bottom: 0.5em", pattern="item", renderer=multiply),
             ]
@@ -84,8 +71,8 @@
                 td["AGE"]
             ],
 
-            div(renderer=sequence)[
-                tr(pattern="item", renderer=slotFiller)[
+            div(renderer=stan.sequence)[
+                tr(pattern="item", renderer=stan.mapping)[
                     td(style="background-color: #efefef", slot="name"),
                     td(style="background-color: #abcdef", slot="age"),
                 ]
@@ -95,3 +82,6 @@
                 renderer=selectOptioner)
     ]
 ]
+
+application = service.Application("simple")
+internet.TCPServer(8080, server.Site(Simple())).setServiceParent(application)
Index: stan.py
===================================================================
RCS file: /cvs/Quotient/nevow/stan.py,v
retrieving revision 1.15
diff -u -r1.15 stan.py
--- stan.py	2 Jan 2004 16:02:08 -0000	1.15
+++ stan.py	8 Jan 2004 22:11:58 -0000
@@ -3,6 +3,8 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
+from __future__ import generators
+
 
 class Proto(str):
     """Proto is a string subclass. Instances of Proto, which are constructed
@@ -124,19 +128,29 @@
         """
         raise NotImplementedError, "Stan tag instances are not iterable."
 
+    def _clone(self, obj, deep):
+        if hasattr(obj, 'clone'):
+            return obj.clone(deep)
+        elif isinstance(obj, (list, tuple)):
+            return [self._clone(x, deep)
+                    for x in obj]
+        else:
+            return obj
+        
     def clone(self, deep=True):
         """Return a clone of this tag. If deep is True, clone all of this
         tag's children. Otherwise, the children list of the clone will
         be empty.
         """
         if deep:
-            newchildren = [
-                hasattr(ch, 'clone') and ch.clone() or ch
-                for ch in self.children
-            ]
+            newchildren = [self._clone(x, True) for x in self.children]
         else:
             newchildren = []
-        return Tag(self.tagName, attributes=self.attributes.copy(), children=newchildren, specials=self._specials.copy())
+        newattrs = self.attributes.copy()
+        for key in newattrs:
+            newattrs[key]=self._clone(newattrs[key], True)
+        
+        return Tag(self.tagName, attributes=newattrs, children=newchildren, specials=self._specials.copy())
 
     def clear(self):
         """Clear any existing children from this tag.
@@ -156,9 +170,16 @@
         return "Tag(%r%s)" % (self.tagName, rstr)
 
 
+class UnsetClass:
+    def __nonzero__(self):
+        return False
+    def __repr__(self):
+        return "Unset"
+Unset=UnsetClass()
+
 def makeAccessors(special):
     def getSpecial(self):
-        return self._specials.get(special)
+        return self._specials.get(special, Unset)
 
     def setSpecial(self, data):
         self._specials[special] = data
@@ -171,31 +192,33 @@
 del name
 
 
-def specials(tagOrContext, special):
+def specials(tag, special):
     """Generate tags with special attributes regardless of attribute value.
     """
-    tag = getattr(tagOrContext, 'tag', tagOrContext)
-    if getattr(tag, special, None) is not None:
-        yield tag
-    else:
-        for child in getattr(tag, 'children', []):
+    for childOrContext in getattr(tag, 'children', []):
+        child = getattr(childOrContext, 'tag', childOrContext)
+        
+        if getattr(child, special, Unset) is not Unset:
+            yield child
+        else:
             for match in specials(child, special):
                 yield match
 
 
-def specialMatches(tagOrContext, special, pattern):
+def specialMatches(tag, special, pattern):
     """Generate special attribute matches starting with the given tag;
     if a match is found, do not look any deeper below that match.
     """
-    tag = getattr(tagOrContext, 'tag', tagOrContext)
-    if getattr(tag, special, None) == pattern:
-        yield tag
-    else:
-        for child in getattr(tag, 'children', []):
+    for childOrContext in getattr(tag, 'children', []):
+        child = getattr(childOrContext, 'tag', childOrContext)
+        
+        data = getattr(child, special, Unset)
+        if data == pattern:
+            yield child
+        elif data is Unset:
             for match in specialMatches(child, special, pattern):
                 yield match
 
-
 def sequence(context, data):
     headers = specialMatches(context.tag, 'pattern', 'header')
     pattern = context.patterns('item')
Index: url.py
===================================================================
RCS file: /cvs/Quotient/nevow/url.py,v
retrieving revision 1.5
diff -u -r1.5 url.py
--- url.py	23 Dec 2003 06:15:38 -0000	1.5
+++ url.py	8 Jan 2004 22:11:58 -0000
@@ -1,4 +1,4 @@
-
+from __future__ import generators
 import urllib
 import weakref
 
Index: serial/flatmdom.py
===================================================================
RCS file: /cvs/Quotient/nevow/serial/flatmdom.py,v
retrieving revision 1.10
diff -u -r1.10 flatmdom.py
--- serial/flatmdom.py	11 Dec 2003 03:22:04 -0000	1.10
+++ serial/flatmdom.py	8 Jan 2004 22:11:58 -0000
@@ -3,6 +3,8 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
+from __future__ import generators
+
 from nevow.iwoven import ISerializable
 from nevow.stan import Tag, xml, directive
 
Index: serial/flatstan.py
===================================================================
RCS file: /cvs/Quotient/nevow/serial/flatstan.py,v
retrieving revision 1.18
diff -u -r1.18 flatstan.py
--- serial/flatstan.py	17 Dec 2003 01:44:39 -0000	1.18
+++ serial/flatstan.py	8 Jan 2004 22:11:58 -0000
@@ -3,11 +3,12 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
-
+from __future__ import generators
+from types import StringTypes
 import types
 import warnings
 
 from twisted.python import components, log
 from nevow.stan import Proto, Tag, xml, directive
 from nevow.iwoven import ISerializable, IRendererFactory, IData
 from nevow.renderer import flatten
@@ -22,7 +24,7 @@
 
 class TagSerializer(components.Adapter):
     __implements__ = ISerializable,
-
+    
     def serialize(self, context, stream):
         visible = bool(self.original.tagName)
         singleton = not self.original.renderer and not self.original.children and not self.original.data
@@ -50,7 +52,7 @@
                             flat = flatten(ISerializable(v).serialize(context, stream))
                             if flat:
                                 val = flat[0]
-                                if isinstance(val, basestring):
+                                if isinstance(val, StringTypes):
                                     val = val.replace('"', '"')
                                 yield xml(val)
                         yield xml('"')
@@ -63,9 +65,9 @@
                 # TODO: Make this less buggy.
                 try:
                     if context.locate(IData) != self.original.data:
-                        context = context.with(self.original)                    
+                        context = context.with(self.original)
                 except KeyError:
-                    context = context.with(self.original)                    
+                    context = context.with(self.original)
                 if self.original.renderer:
                     toBeRenderedBy = self.original.renderer
                     self.original.renderer = None
Index: test/test_disktemplate.py
===================================================================
RCS file: /cvs/Quotient/nevow/test/test_disktemplate.py,v
retrieving revision 1.16
diff -u -r1.16 test_disktemplate.py
--- test/test_disktemplate.py	11 Dec 2003 01:04:10 -0000	1.16
+++ test/test_disktemplate.py	8 Jan 2004 22:11:58 -0000
@@ -3,6 +3,7 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
+from __future__ import generators
 
 from nevow import renderer
 from nevow import util
Index: test/test_flatstan.py
===================================================================
RCS file: /cvs/Quotient/nevow/test/test_flatstan.py,v
retrieving revision 1.15
diff -u -r1.15 test_flatstan.py
--- test/test_flatstan.py	10 Dec 2003 19:48:49 -0000	1.15
+++ test/test_flatstan.py	8 Jan 2004 22:11:58 -0000
@@ -3,6 +3,8 @@
 # and/or modify it under the terms of version 2.1 of the GNU Lesser General
 # Public License as published by the Free Software Foundation.
 
+from __future__ import generators
+
 from twisted.python import reflect
 
 from twisted.web.resource import IResource

--Apple-Mail-4-807131218
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=US-ASCII;
	format=flowed



The deferred freeform patch from Gavrie Philipson should probably also 
go in.

James

--Apple-Mail-4-807131218--