r47281 - Implement flattening optimization.

mithrandi-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org Sun, 17 Apr 2016 02:54:10 -0600 (MDT)
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: mithrandi
Date: Sun Apr 17 02:54:05 2016
New Revision: 47281

Modified:
   branches/flatten-optimization-8300/twisted/web/_flatten.py

Log:
Implement flattening optimization.

Modified: branches/flatten-optimization-8300/twisted/web/_flatten.py
==============================================================================
--- branches/flatten-optimization-8300/twisted/web/_flatten.py	(original)
+++ branches/flatten-optimization-8300/twisted/web/_flatten.py	Sun Apr 17 02:54:05 2016
@@ -69,10 +69,10 @@
 
 
 
-def flattenWithAttributeEscaping(root):
+def writeWithAttributeEscaping(write):
     """
-    Decorate the generator returned by L{_flattenElement} so that its output is
-    properly quoted for inclusion within an XML attribute value.
+    Decorate a C{write} callable so that all output written is properly quoted
+    for inclusion within an XML attribute value.
 
     If a L{Tag <twisted.web.template.Tag>} C{x} is flattened within the context
     of the contents of another L{Tag <twisted.web.template.Tag>} C{y}, the
@@ -94,38 +94,22 @@
     comments and CDATA, so if you were to serialize a L{comment
     <twisted.web.template.Comment>} in an attribute you should get C{<y
     attr="&lt;-- comment --&gt;" />}.  Therefore in order to capture these
-    meta-characters, the attribute generator from L{_flattenElement} context is
-    wrapped with an L{flattenWithAttributeEscaping}.
-
-    Because I{all} characters serialized in the context of an attribute are
-    quoted before they are yielded by the generator returned by
-    L{flattenWithAttributeEscaping}, on the "outside" of the L{_flattenElement}
-    call, the L{_flattenElement} generator therefore no longer needs to quote
-    text that appears directly within the attribute itself.
+    meta-characters, flattening is done with C{write} callable that is wrapped
+    with L{writeWithAttributeEscaping}.
 
     The final case, and hopefully the much more common one as compared to
     serializing L{Tag <twisted.web.template.Tag>} and arbitrary L{IRenderable}
     objects within an attribute, is to serialize a simple string, and those
-    should be passed through for L{flattenWithAttributeEscaping} to quote
+    should be passed through for L{writeWithAttributeEscaping} to quote
     without applying a second, redundant level of quoting.
 
-    @param root: A value that may be yielded by L{_flattenElement}; either an
-        iterable yielding L{bytes} (or more iterables), or bytes itself.
-    @type root: L{bytes} or C{iterable}
-
-    @return: The same type as L{_flattenElement} returns, with all the bytes
-        encoded for representation within an attribute.
-    @rtype: the same type as the C{subFlatten} argument
-    """
-    if isinstance(root, bytes):
-        root = escapeForContent(root)
-        root = root.replace(b'"', b'&quot;')
-        yield root
-    elif isinstance(root, Deferred):
-        yield root.addCallback(flattenWithAttributeEscaping)
-    else:
-        for subroot in root:
-            yield flattenWithAttributeEscaping(subroot)
+    @param write: A callable which will be invoked with the escaped L{bytes}.
+
+    @return: A callable that writes data with escaping.
+    """
+    def _write(data):
+        write(escapeForContent(data).replace(b'"', b'&quot;'))
+    return _write
 
 
 
@@ -180,7 +164,8 @@
 
 
 
-def _flattenElement(request, root, slotData, renderFactory, dataEscaper):
+def _flattenElement(request, root, write, slotData, renderFactory,
+                    dataEscaper):
     """
     Make C{root} slightly more flat by yielding all its immediate contents as
     strings, deferreds or generators that are recursive calls to itself.
@@ -193,6 +178,9 @@
         L{GeneratorType}, L{Deferred}, or an object that implements
         L{IRenderable}.
 
+    @param write: A callable which will be invoked with each L{bytes} produced
+        by flattening C{root}.
+
     @param slotData: A C{list} of C{dict} mapping C{str} slot names to data
         with which those slots will be replaced.
 
@@ -204,7 +192,7 @@
         rendering context.  This is really only one of two values:
         L{attributeEscapingDoneOutside} or L{escapeForContent}, depending on
         whether the rendering context is within an attribute or not.  See the
-        explanation in L{flattenWithAttributeEscaping}.
+        explanation in L{writeWithAttributeEscaping}.
 
     @return: An iterator that eventually yields L{bytes} that should be written
         to the output.  However it may also yield other iterators or
@@ -217,22 +205,22 @@
         of the same type.
     """
     def keepGoing(newRoot, dataEscaper=dataEscaper,
-                  renderFactory=renderFactory):
-        return _flattenElement(request, newRoot, slotData, renderFactory,
-                               dataEscaper)
+                  renderFactory=renderFactory, write=write):
+        return _flattenElement(request, newRoot, write, slotData,
+                               renderFactory, dataEscaper)
     if isinstance(root, (bytes, unicode)):
-        yield dataEscaper(root)
+        write(dataEscaper(root))
     elif isinstance(root, slot):
         slotValue = _getSlotValue(root.name, slotData, root.default)
         yield keepGoing(slotValue)
     elif isinstance(root, CDATA):
-        yield b'<![CDATA['
-        yield escapedCDATA(root.data)
-        yield b']]>'
+        write(b'<![CDATA[')
+        write(escapedCDATA(root.data))
+        write(b']]>')
     elif isinstance(root, Comment):
-        yield b'<!--'
-        yield escapedComment(root.data)
-        yield b'-->'
+        write(b'<!--')
+        write(escapedComment(root.data))
+        write(b'-->')
     elif isinstance(root, Tag):
         slotData.append(root.slotData)
         if root.render is not None:
@@ -249,23 +237,25 @@
             yield keepGoing(root.children)
             return
 
-        yield b'<'
+        write(b'<')
         if isinstance(root.tagName, unicode):
             tagName = root.tagName.encode('ascii')
         else:
             tagName = root.tagName
-        yield tagName
+        write(tagName)
         for k, v in iteritems(root.attributes):
             if isinstance(k, unicode):
                 k = k.encode('ascii')
-            yield b' ' + k + b'="'
+            write(b' ' + k + b'="')
             # Serialize the contents of the attribute, wrapping the results of
             # that serialization so that _everything_ is quoted.
-            attribute = keepGoing(v, attributeEscapingDoneOutside)
-            yield flattenWithAttributeEscaping(attribute)
-            yield b'"'
+            yield keepGoing(
+                v,
+                attributeEscapingDoneOutside,
+                write=writeWithAttributeEscaping(write))
+            write(b'"')
         if root.children or nativeString(tagName) not in voidElements:
-            yield b'>'
+            write(b'>')
             # Regardless of whether we're in an attribute or not, switch back
             # to the escapeForContent dataEscaper.  The contents of a tag must
             # be quoted no matter what; in the top-level document, just so
@@ -274,16 +264,16 @@
             # parse the tag within the attribute, all the quoting is still
             # correct.
             yield keepGoing(root.children, escapeForContent)
-            yield b'</' + tagName + b'>'
+            write(b'</' + tagName + b'>')
         else:
-            yield b' />'
+            write(b' />')
 
     elif isinstance(root, (tuple, list, GeneratorType)):
         for element in root:
             yield keepGoing(element)
     elif isinstance(root, CharRef):
         escaped = '&#%d;' % (root.ordinal,)
-        yield escaped.encode('ascii')
+        write(escaped.encode('ascii'))
     elif isinstance(root, Deferred):
         yield root.addCallback(lambda result: (result, keepGoing(result)))
     elif IRenderable.providedBy(root):
@@ -294,7 +284,7 @@
 
 
 
-def _flattenTree(request, root):
+def _flattenTree(request, root, write):
     """
     Make C{root} into an iterable of L{bytes} and L{Deferred} by doing a depth
     first traversal of the tree.
@@ -307,12 +297,15 @@
         L{list}, L{GeneratorType}, L{Deferred}, or something providing
         L{IRenderable}.
 
+    @param write: A callable which will be invoked with each L{bytes} produced
+        by flattening C{root}.
+
     @return: An iterator which yields objects of type L{bytes} and L{Deferred}.
         A L{Deferred} is only yielded when one is encountered in the process of
         flattening C{root}.  The returned iterator must not be iterated again
         until the L{Deferred} is called back.
     """
-    stack = [_flattenElement(request, root, [], None, escapeForContent)]
+    stack = [_flattenElement(request, root, write, [], None, escapeForContent)]
     while stack:
         try:
             frame = stack[-1].gi_frame
@@ -328,7 +321,7 @@
             raise FlattenerError(e, roots, extract_tb(exc_info()[2]))
         else:
             if type(element) is bytes:
-                yield element
+                assert False
             elif isinstance(element, Deferred):
                 def cbx(originalAndToFlatten):
                     original, toFlatten = originalAndToFlatten
@@ -366,8 +359,7 @@
             result.errback()
         else:
             if type(element) is bytes:
-                write(element)
-                continue
+                assert False
             else:
                 def cby(original):
                     _writeFlattenedData(state, write, result)
@@ -401,7 +393,7 @@
         unexpected exception occurs.
     """
     result = Deferred()
-    state = _flattenTree(request, root)
+    state = _flattenTree(request, root, write)
     _writeFlattenedData(state, write, result)
     return result