changeset 1026:2e169deb0adc stable-0.7.x

Merge r1229 to r1238 and r1243 to r1251 from trunk (documentation fixes and Python 3.4 support).
author hodgestar
date Sun, 09 Mar 2014 08:41:45 +0000
parents 9c4fafa85f4a
children 4cbbf894c800
files doc/upgrade.txt genshi/compat.py genshi/filters/tests/test_html.py genshi/template/astutil.py genshi/template/eval.py genshi/template/tests/eval.py run_benchmarks.sh setup.py
diffstat 8 files changed, 151 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/doc/upgrade.txt
+++ b/doc/upgrade.txt
@@ -7,11 +7,11 @@
    :depth: 2
 .. sectnum::
 
-------------------------------------------------------
-Upgrading from Genshi 0.6.x to the development version
-------------------------------------------------------
+-------------------------------------------
+Upgrading from Genshi 0.6.x to Genshi 0.7.x
+-------------------------------------------
 
-The Genshi development version now supports both Python 2 and Python 3.
+Genshi 0.7.x now supports both Python 2 and Python 3.
 
 The most noticable API change in the Genshi development version is that the
 default encoding in numerous places is now None (i.e. unicode) instead
--- a/genshi/compat.py
+++ b/genshi/compat.py
@@ -35,6 +35,15 @@
                 'Python 2 compatibility function. Not usable in Python 3.')
 
 
+# We need to test if an object is an instance of a string type in places
+
+if IS_PYTHON2:
+    def isstring(obj):
+        return isinstance(obj, basestring)
+else:
+    def isstring(obj):
+        return isinstance(obj, str)
+
 # We need to differentiate between StringIO and BytesIO in places
 
 if IS_PYTHON2:
@@ -112,4 +121,3 @@
             if not x:
                 return False
         return True
-
--- a/genshi/filters/tests/test_html.py
+++ b/genshi/filters/tests/test_html.py
@@ -368,12 +368,16 @@
 
 class HTMLSanitizerTestCase(unittest.TestCase):
 
-    def assert_parse_error_or_equal(self, expected, exploit):
+    def assert_parse_error_or_equal(self, expected, exploit,
+                                    allow_strip=False):
         try:
             html = HTML(exploit)
         except ParseError:
             return
-        self.assertEquals(expected, (html | HTMLSanitizer()).render())
+        sanitized_html = (html | HTMLSanitizer()).render()
+        if not sanitized_html and allow_strip:
+            return
+        self.assertEquals(expected, sanitized_html)
 
     def test_sanitize_unchanged(self):
         html = HTML(u'<a href="#">fo<br />o</a>')
@@ -416,10 +420,12 @@
         html = HTML(u'<SCRIPT SRC="http://example.com/"></SCRIPT>')
         self.assertEquals('', (html | HTMLSanitizer()).render())
         src = u'<SCR\0IPT>alert("foo")</SCR\0IPT>'
-        self.assert_parse_error_or_equal('&lt;SCR\x00IPT&gt;alert("foo")', src)
+        self.assert_parse_error_or_equal('&lt;SCR\x00IPT&gt;alert("foo")', src,
+                                         allow_strip=True)
         src = u'<SCRIPT&XYZ SRC="http://example.com/"></SCRIPT>'
         self.assert_parse_error_or_equal('&lt;SCRIPT&amp;XYZ; '
-                                         'SRC="http://example.com/"&gt;', src)
+                                         'SRC="http://example.com/"&gt;', src,
+                                         allow_strip=True)
 
     def test_sanitize_remove_onclick_attr(self):
         html = HTML(u'<div onclick=\'alert("foo")\' />')
--- a/genshi/template/astutil.py
+++ b/genshi/template/astutil.py
@@ -21,7 +21,7 @@
     def parse(source, mode):
         return compile(source, '', mode, _ast.PyCF_ONLY_AST)
 
-from genshi.compat import IS_PYTHON2
+from genshi.compat import IS_PYTHON2, isstring
 
 __docformat__ = 'restructuredtext en'
 
@@ -103,32 +103,48 @@
         self._new_line()
         return self.visit(node.body)
 
+    # Python < 3.4
     # arguments = (expr* args, identifier? vararg,
     #              identifier? kwarg, expr* defaults)
+    #
+    # Python >= 3.4
+    # arguments = (arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults,
+    #              arg? kwarg, expr* defaults)
     def visit_arguments(self, node):
-        first = True
-        no_default_count = len(node.args) - len(node.defaults)
-        for i, arg in enumerate(node.args):
-            if not first:
-                self._write(', ')
+        def write_possible_comma():
+            if _first[0]:
+                _first[0] = False
             else:
-                first = False
-            self.visit(arg)
-            if i >= no_default_count:
-                self._write('=')
-                self.visit(node.defaults[i - no_default_count])
-        if getattr(node, 'vararg', None):
-            if not first:
                 self._write(', ')
+        _first = [True]
+
+        def write_args(args, defaults):
+            no_default_count = len(args) - len(defaults)
+            for i, arg in enumerate(args):
+                write_possible_comma()
+                self.visit(arg)
+                default_idx = i - no_default_count
+                if default_idx >= 0 and defaults[default_idx] is not None:
+                    self._write('=')
+                    self.visit(defaults[i - no_default_count])
+
+        write_args(node.args, node.defaults)
+        if getattr(node, 'vararg', None):
+            write_possible_comma()
+            self._write('*')
+            if isstring(node.vararg):
+                self._write(node.vararg)
             else:
-                first = False
-            self._write('*' + node.vararg)
+                self.visit(node.vararg)
+        if getattr(node, 'kwonlyargs', None):
+            write_args(node.kwonlyargs, node.kw_defaults)
         if getattr(node, 'kwarg', None):
-            if not first:
-                self._write(', ')
+            write_possible_comma()
+            self._write('**')
+            if isstring(node.kwarg):
+                self._write(node.kwarg)
             else:
-                first = False
-            self._write('**' + node.kwarg)
+                self.visit(node.kwarg)
 
     if not IS_PYTHON2:
         # In Python 3 arguments get a special node
@@ -724,6 +740,17 @@
     def visit_Name(self, node):
         self._write(node.id)
 
+    # NameConstant(singleton value)
+    def visit_NameConstant(self, node):
+        if node.value is None:
+            self._write('None')
+        elif node.value is True:
+            self._write('True')
+        elif node.value is False:
+            self._write('False')
+        else:
+            raise Exception("Unknown NameConstant %r" % (node.value,))
+
     # List(expr* elts, expr_context ctx)
     def visit_List(self, node):
         self._write('[')
@@ -829,6 +856,7 @@
     visit_Attribute = _clone
     visit_Subscript = _clone
     visit_Name = _clone
+    visit_NameConstant = _clone
     visit_List = _clone
     visit_Tuple = _clone
 
--- a/genshi/template/eval.py
+++ b/genshi/template/eval.py
@@ -24,7 +24,8 @@
 from genshi.template.base import TemplateRuntimeError
 from genshi.util import flatten
 
-from genshi.compat import get_code_params, build_code_chunk, IS_PYTHON2
+from genshi.compat import get_code_params, build_code_chunk, isstring, \
+                          IS_PYTHON2
 
 __all__ = ['Code', 'Expression', 'Suite', 'LenientLookup', 'StrictLookup',
            'Undefined', 'UndefinedError']
@@ -495,28 +496,34 @@
     def __init__(self):
         self.locals = [CONSTANTS]
 
+    def _process(self, names, node):
+        if not IS_PYTHON2 and isinstance(node, _ast.arg):
+            names.add(node.arg)
+        elif isstring(node):
+            names.add(node)
+        elif isinstance(node, _ast.Name):
+            names.add(node.id)
+        elif isinstance(node, _ast.alias):
+            names.add(node.asname or node.name)
+        elif isinstance(node, _ast.Tuple):
+            for elt in node.elts:
+                self._process(names, elt)
+
     def _extract_names(self, node):
         names = set()
-        def _process(node):
-            if not IS_PYTHON2 and isinstance(node, _ast.arg):
-                names.add(node.arg)
-            if isinstance(node, _ast.Name):
-                names.add(node.id)
-            elif isinstance(node, _ast.alias):
-                names.add(node.asname or node.name)
-            elif isinstance(node, _ast.Tuple):
-                for elt in node.elts:
-                    _process(elt)
         if hasattr(node, 'args'):
             for arg in node.args:
-                _process(arg)
+                self._process(names, arg)
+            if hasattr(node, 'kwonlyargs'):
+                for arg in node.kwonlyargs:
+                    self._process(names, arg)
             if hasattr(node, 'vararg'):
-                names.add(node.vararg)
+                self._process(names, node.vararg)
             if hasattr(node, 'kwarg'):
-                names.add(node.kwarg)
+                self._process(names, node.kwarg)
         elif hasattr(node, 'names'):
             for elt in node.names:
-                _process(elt)
+                self._process(names, elt)
         return names
 
     def visit_Str(self, node):
--- a/genshi/template/tests/eval.py
+++ b/genshi/template/tests/eval.py
@@ -590,6 +590,29 @@
         suite.execute(data)
         self.assertEqual(['bardef', 'fooabc'], sorted(data['x']))
 
+    if not IS_PYTHON2:
+        def test_def_kwonlyarg(self):
+            suite = Suite("""
+def kwonly(*args, k):
+    return k
+x = kwonly(k="foo")
+""")
+            data = {}
+            suite.execute(data)
+            self.assertEqual("foo", data['x'])
+
+        def test_def_kwonlyarg_with_default(self):
+            suite = Suite("""
+def kwonly(*args, k="bar"):
+    return k
+x = kwonly(k="foo")
+y = kwonly()
+""")
+            data = {}
+            suite.execute(data)
+            self.assertEqual("foo", data['x'])
+            self.assertEqual("bar", data['y'])
+
     def test_def_nested(self):
         suite = Suite("""
 def doit():
new file mode 100644
--- /dev/null
+++ b/run_benchmarks.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# 1. Run the tests with `tox` (this will set up all the tox envs).
+# 2. ./run_benchmarks.sh <env-name> | tee results-<env-name>.out
+
+NAME="$1"
+PYTHON="./.tox/$NAME/bin/python"
+BENCH_DIR="bench_build/$1"
+BENCH_BIN_DIR="$BENCH_DIR/bin"
+mkdir -p "bench_build"
+
+rm -rf "$BENCH_DIR"
+cp -R "examples/bench" "$BENCH_DIR"
+
+case "$NAME" in
+  py32|py33)
+    2to3 -w --no-diffs "$BENCH_DIR"
+    ;;
+esac
+
+echo "-- basic --"
+"$PYTHON" "$BENCH_DIR/basic.py" 
+echo
+
+echo "-- bigtable --"
+"$PYTHON" "$BENCH_DIR/bigtable.py"
+echo
+
+echo "-- xpath --"
+"$PYTHON" "$BENCH_DIR/xpath.py"
+echo
--- a/setup.py
+++ b/setup.py
@@ -65,9 +65,13 @@
 
 
 if Feature:
+    # Optional C extension module for speeding up Genshi:
+    # Not activated by default on:
+    # - PyPy (where it harms performance)
+    # - CPython >= 3.3 (the new Unicode C API is not supported yet)
     speedups = Feature(
         "optional C speed-enhancements",
-        standard = not is_pypy,
+        standard = not is_pypy and sys.version_info < (3, 3),
         ext_modules = [
             Extension('genshi._speedups', ['genshi/_speedups.c']),
         ],
Copyright (C) 2012-2017 Edgewall Software