summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--docs/source/changes.rst4
-rw-r--r--docs/source/conf.py4
-rw-r--r--pecan/__init__.py20
-rw-r--r--pecan/commands/__init__.py8
-rw-r--r--pecan/commands/base.py40
-rw-r--r--pecan/commands/create.py2
-rw-r--r--pecan/commands/serve.py11
-rw-r--r--pecan/commands/shell.py11
-rw-r--r--pecan/compat/__init__.py48
-rw-r--r--pecan/compat/dictconfig.py560
-rw-r--r--pecan/configuration.py19
-rw-r--r--pecan/core.py40
-rw-r--r--pecan/decorators.py12
-rw-r--r--pecan/deploy.py2
-rw-r--r--pecan/hooks.py25
-rw-r--r--pecan/jsonify.py7
-rw-r--r--pecan/middleware/debug.py3
-rw-r--r--pecan/middleware/errordocument.py12
-rw-r--r--pecan/middleware/recursive.py2
-rw-r--r--pecan/middleware/resources/__init__.py9
-rw-r--r--pecan/middleware/static.py8
-rw-r--r--pecan/rest.py8
-rw-r--r--pecan/routing.py4
-rw-r--r--pecan/scaffolds/__init__.py22
-rw-r--r--pecan/scaffolds/base/+package+/model/__init__.py2
-rw-r--r--pecan/secure.py112
-rw-r--r--pecan/templating.py13
-rw-r--r--pecan/tests/compat/__init__.py0
-rw-r--r--pecan/tests/compat/test_dictconfig.py733
-rw-r--r--pecan/tests/middleware/test_debug.py7
-rw-r--r--pecan/tests/middleware/test_errordocument.py16
-rw-r--r--pecan/tests/middleware/test_recursive.py11
-rw-r--r--pecan/tests/scaffold_builder.py26
-rw-r--r--pecan/tests/test_base.py282
-rw-r--r--pecan/tests/test_generic.py6
-rw-r--r--pecan/tests/test_hooks.py55
-rw-r--r--pecan/tests/test_jsonify.py6
-rw-r--r--pecan/tests/test_rest.py210
-rw-r--r--pecan/tests/test_scaffolds.py4
-rw-r--r--pecan/tests/test_secure.py41
-rw-r--r--pecan/tests/test_templating.py8
-rw-r--r--requirements.txt3
-rw-r--r--setup.py34
-rw-r--r--tox.ini30
45 files changed, 663 insertions, 1819 deletions
diff --git a/.travis.yml b/.travis.yml
index 61a18de..61757d2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,8 @@ language: python
python:
- "2.6"
- "2.7"
+ - "3.2"
+ - "3.3"
env:
- PYTHONPATH='.'
branches:
diff --git a/docs/source/changes.rst b/docs/source/changes.rst
index b2d6540..2356324 100644
--- a/docs/source/changes.rst
+++ b/docs/source/changes.rst
@@ -1,3 +1,7 @@
+0.3.0b
+======
+* Pecan now supports Python 2.6, 2.7, 3.2, and 3.3.
+
0.2.4
=====
* Add support for ``_lookup`` methods as a fallback in RestController.
diff --git a/docs/source/conf.py b/docs/source/conf.py
index c331fe3..6913c74 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -49,9 +49,9 @@ copyright = u'2010, Jonathan LaCour'
# built documents.
#
# The short X.Y version.
-version = '0.2.4'
+version = '0.3.0'
# The full version, including alpha/beta/rc tags.
-release = '0.2.4'
+release = '0.3.0b'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/pecan/__init__.py b/pecan/__init__.py
index 11db082..cf175b8 100644
--- a/pecan/__init__.py
+++ b/pecan/__init__.py
@@ -1,21 +1,21 @@
-from core import (
+from .core import (
abort, override_template, Pecan, load_app, redirect, render,
request, response
)
-from decorators import expose
-from hooks import RequestViewerHook
-from middleware.debug import DebugMiddleware
-from middleware.errordocument import ErrorDocumentMiddleware
-from middleware.recursive import RecursiveMiddleware
-from middleware.static import StaticFileMiddleware
+from .decorators import expose
+from .hooks import RequestViewerHook
+from .middleware.debug import DebugMiddleware
+from .middleware.errordocument import ErrorDocumentMiddleware
+from .middleware.recursive import RecursiveMiddleware
+from .middleware.static import StaticFileMiddleware
-from configuration import set_config, Config
-from configuration import _runtime_conf as conf
+from .configuration import set_config, Config
+from .configuration import _runtime_conf as conf
try:
from logging.config import dictConfig as load_logging_config
except ImportError:
- from .compat.dictconfig import dictConfig as load_logging_config # noqa
+ from logutils.dictconfig import dictConfig as load_logging_config # noqa
__all__ = [
diff --git a/pecan/commands/__init__.py b/pecan/commands/__init__.py
index db5aafe..da02464 100644
--- a/pecan/commands/__init__.py
+++ b/pecan/commands/__init__.py
@@ -1,4 +1,4 @@
-from base import CommandRunner, BaseCommand # noqa
-from serve import ServeCommand # noqa
-from shell import ShellCommand # noqa
-from create import CreateCommand # noqa
+from .base import CommandRunner, BaseCommand # noqa
+from .serve import ServeCommand # noqa
+from .shell import ShellCommand # noqa
+from .create import CreateCommand # noqa
diff --git a/pecan/commands/base.py b/pecan/commands/base.py
index 68b5c0e..2a79ca5 100644
--- a/pecan/commands/base.py
+++ b/pecan/commands/base.py
@@ -1,10 +1,11 @@
import pkg_resources
-import os.path
import argparse
import logging
import sys
from warnings import warn
+import six
+
log = logging.getLogger(__name__)
@@ -46,7 +47,7 @@ class CommandManager(object):
try:
cmd = ep.load()
assert hasattr(cmd, 'run')
- except Exception, e: # pragma: nocover
+ except Exception as e: # pragma: nocover
warn("Unable to load plugin %s: %s" % (ep, e), RuntimeWarning)
continue
self.add({ep.name: cmd})
@@ -60,9 +61,11 @@ class CommandRunner(object):
def __init__(self):
self.manager = CommandManager()
- self.parser = HelpfulArgumentParser(
- version='Pecan %s' % self.version,
- add_help=True
+ self.parser = HelpfulArgumentParser(add_help=True)
+ self.parser.add_argument(
+ '--version',
+ action='version',
+ version='Pecan %s' % self.version
)
self.parse_sub_commands()
@@ -78,7 +81,7 @@ class CommandRunner(object):
)
for arg in getattr(cmd, 'arguments', tuple()):
arg = arg.copy()
- if isinstance(arg.get('name'), basestring):
+ if isinstance(arg.get('name'), six.string_types):
sub.add_argument(arg.pop('name'), **arg)
elif isinstance(arg.get('name'), list):
sub.add_argument(*arg.pop('name'), **arg)
@@ -101,7 +104,20 @@ class CommandRunner(object):
return self.manager.commands
-class BaseCommand(object):
+class BaseCommandMeta(type):
+
+ @property
+ def summary(cls):
+ """
+ This is used to populate the --help argument on the command line.
+
+ This provides a default behavior which takes the first sentence of the
+ command's docstring and uses it.
+ """
+ return cls.__doc__.strip().splitlines()[0].rstrip('.')
+
+
+class BaseCommandParent(object):
"""
A base interface for Pecan commands.
@@ -126,14 +142,10 @@ class BaseCommand(object):
def run(self, args):
super(SomeCommand, self).run(args)
- print args.extra_arg
+ if args.extra_arg:
+ pass
"""
- class __metaclass__(type):
- @property
- def summary(cls):
- return cls.__doc__.strip().splitlines()[0].rstrip('.')
-
arguments = ({
'name': 'config_file',
'help': 'a Pecan configuration file',
@@ -147,3 +159,5 @@ class BaseCommand(object):
def load_app(self):
from pecan import load_app
return load_app(self.args.config_file)
+
+BaseCommand = BaseCommandMeta('BaseCommand', (BaseCommandParent,), {})
diff --git a/pecan/commands/create.py b/pecan/commands/create.py
index f332065..b6eec2a 100644
--- a/pecan/commands/create.py
+++ b/pecan/commands/create.py
@@ -23,7 +23,7 @@ class ScaffoldManager(object):
try:
cmd = ep.load()
assert hasattr(cmd, 'copy_to')
- except Exception, e: # pragma: nocover
+ except Exception as e: # pragma: nocover
warn(
"Unable to load scaffold %s: %s" % (ep, e), RuntimeWarning
)
diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py
index 9dd431e..0d1b14a 100644
--- a/pecan/commands/serve.py
+++ b/pecan/commands/serve.py
@@ -1,6 +1,7 @@
"""
Serve command for Pecan.
"""
+from __future__ import print_function
import os
import sys
import time
@@ -42,7 +43,7 @@ class ServeCommand(BaseCommand):
DirModifiedEvent
)
- print 'Monitoring for changes...'
+ print('Monitoring for changes...')
self.create_subprocess()
parent = self
@@ -101,13 +102,15 @@ class ServeCommand(BaseCommand):
host, port = conf.server.host, int(conf.server.port)
srv = make_server(host, port, app)
- print 'Starting server in PID %s' % os.getpid()
+ print('Starting server in PID %s' % os.getpid())
if host == '0.0.0.0':
- print 'serving on 0.0.0.0:%s, view at http://127.0.0.1:%s' % \
+ print(
+ 'serving on 0.0.0.0:%s, view at http://127.0.0.1:%s' %
(port, port)
+ )
else:
- print "serving on http://%s:%s" % (host, port)
+ print("serving on http://%s:%s" % (host, port))
try:
srv.serve_forever()
diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py
index 8f7a38a..968ded0 100644
--- a/pecan/commands/shell.py
+++ b/pecan/commands/shell.py
@@ -125,9 +125,12 @@ class ShellCommand(BaseCommand):
locs['model'] = model
# insert the pecan locals
- exec(
- 'from pecan import abort, conf, redirect, request, response'
- ) in locs
+ from pecan import abort, conf, redirect, request, response
+ locs['abort'] = abort
+ locs['conf'] = conf
+ locs['redirect'] = redirect
+ locs['request'] = request
+ locs['response'] = response
# prepare the banner
banner = ' The following objects are available:\n'
@@ -153,7 +156,7 @@ class ShellCommand(BaseCommand):
shell = self.SHELLS[self.args.shell]
try:
shell().invoke(locs, banner)
- except ImportError, e:
+ except ImportError as e:
warn((
"%s is not installed, `%s`, "
"falling back to native shell") % (self.args.shell, e),
diff --git a/pecan/compat/__init__.py b/pecan/compat/__init__.py
index ac82f91..93883f4 100644
--- a/pecan/compat/__init__.py
+++ b/pecan/compat/__init__.py
@@ -1,40 +1,18 @@
-import sys
+import inspect
-# True if we are running on Python 3.
-PY3 = sys.version_info[0] == 3
+import six
-if PY3: # pragma: no cover
- text_type = str
+if six.PY3:
+ import urllib.parse as urlparse
+ from urllib.parse import quote, unquote_plus
+ from urllib.request import urlopen, URLError
+ from html import escape
else:
- text_type = unicode
+ import urlparse # noqa
+ from urllib import quote, unquote_plus # noqa
+ from urllib2 import urlopen, URLError # noqa
+ from cgi import escape # noqa
-def bytes_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``text_type``, return
- ``s.encode(encoding, errors)``, otherwise return ``s``"""
- if isinstance(s, text_type): # pragma: no cover
- return s.encode(encoding, errors)
- return s
-
-if PY3: # pragma: no cover
- def native_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``text_type``, return
- ``s``, otherwise return ``str(s, encoding, errors)``"""
- if isinstance(s, text_type):
- return s
- return str(s, encoding, errors)
-else:
- def native_(s, encoding='latin-1', errors='strict'): # noqa
- """ If ``s`` is an instance of ``text_type``, return
- ``s.encode(encoding, errors)``, otherwise return ``str(s)``"""
- if isinstance(s, text_type):
- return s.encode(encoding, errors)
- return str(s)
-
-native_.__doc__ = """
-Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise
-return ``str(s, encoding, errors)``
-
-Python 2: If ``s`` is an instance of ``text_type``, return
-``s.encode(encoding, errors)``, otherwise return ``str(s)``
-"""
+def is_bound_method(ob):
+ return inspect.ismethod(ob) and six.get_method_self(ob) is not None
diff --git a/pecan/compat/dictconfig.py b/pecan/compat/dictconfig.py
deleted file mode 100644
index d077d00..0000000
--- a/pecan/compat/dictconfig.py
+++ /dev/null
@@ -1,560 +0,0 @@
-# Copyright 2009-2010 by Vinay Sajip. All Rights Reserved.
-#
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted,
-# provided that the above copyright notice appear in all copies and that
-# both that copyright notice and this permission notice appear in
-# supporting documentation, and that the name of Vinay Sajip
-# not be used in advertising or publicity pertaining to distribution
-# of the software without specific, written prior permission.
-# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
-# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import logging.handlers
-import re
-import sys
-import types
-
-IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
-
-
-def valid_ident(s):
- m = IDENTIFIER.match(s)
- if not m:
- raise ValueError('Not a valid Python identifier: %r' % s)
- return True
-
-#
-# This function is defined in logging only in recent versions of Python
-#
-try:
- from logging import _checkLevel
-except ImportError:
- def _checkLevel(level): # noqa
- if isinstance(level, int):
- rv = level
- elif str(level) == level:
- if level not in logging._levelNames:
- raise ValueError('Unknown level: %r' % level)
- rv = logging._levelNames[level]
- else:
- raise TypeError('Level not an integer or a '
- 'valid string: %r' % level)
- return rv
-
-# The ConvertingXXX classes are wrappers around standard Python containers,
-# and they serve to convert any suitable values in the container. The
-# conversion converts base dicts, lists and tuples to their wrapped
-# equivalents, whereas strings which match a conversion format are converted
-# appropriately.
-#
-# Each wrapper should have a configurator attribute holding the actual
-# configurator to use for conversion.
-
-
-class ConvertingDict(dict):
- """A converting dictionary wrapper."""
-
- def __getitem__(self, key):
- value = dict.__getitem__(self, key)
- result = self.configurator.convert(value)
- #If the converted value is different, save for next time
- if value is not result:
- self[key] = result
- if type(result) in (ConvertingDict, ConvertingList,
- ConvertingTuple):
- result.parent = self
- result.key = key
- return result
-
- def get(self, key, default=None):
- value = dict.get(self, key, default)
- result = self.configurator.convert(value)
- #If the converted value is different, save for next time
- if value is not result:
- self[key] = result
- if type(result) in (ConvertingDict, ConvertingList,
- ConvertingTuple):
- result.parent = self
- result.key = key
- return result
-
- def pop(self, key, default=None):
- value = dict.pop(self, key, default)
- result = self.configurator.convert(value)
- if value is not result:
- if type(result) in (ConvertingDict, ConvertingList,
- ConvertingTuple):
- result.parent = self
- result.key = key
- return result
-
-
-class ConvertingList(list):
- """A converting list wrapper."""
- def __getitem__(self, key):
- value = list.__getitem__(self, key)
- result = self.configurator.convert(value)
- #If the converted value is different, save for next time
- if value is not result:
- self[key] = result
- if type(result) in (ConvertingDict, ConvertingList,
- ConvertingTuple):
- result.parent = self
- result.key = key
- return result
-
- def pop(self, idx=-1):
- value = list.pop(self, idx)
- result = self.configurator.convert(value)
- if value is not result:
- if type(result) in (ConvertingDict, ConvertingList,
- ConvertingTuple):
- result.parent = self
- return result
-
-
-class ConvertingTuple(tuple):
- """A converting tuple wrapper."""
- def __getitem__(self, key):
- value = tuple.__getitem__(self, key)
- result = self.configurator.convert(value)
- if value is not result:
- if type(result) in (ConvertingDict, ConvertingList,
- ConvertingTuple):
- result.parent = self
- result.key = key
- return result
-
-
-class BaseConfigurator(object):
- """
- The configurator base class which defines some useful defaults.
- """
-
- CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
-
- WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
- DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
- INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
- DIGIT_PATTERN = re.compile(r'^\d+$')
-
- value_converters = {
- 'ext': 'ext_convert',
- 'cfg': 'cfg_convert',
- }
-
- # We might want to use a different one, e.g. importlib
- importer = __import__
-
- def __init__(self, config):
- self.config = ConvertingDict(config)
- self.config.configurator = self
-
- def resolve(self, s):
- """
- Resolve strings to objects using standard import and attribute
- syntax.
- """
- name = s.split('.')
- used = name.pop(0)
- try:
- found = self.importer(used)
- for frag in name:
- used += '.' + frag
- try:
- found = getattr(found, frag)
- except AttributeError:
- self.importer(used)
- found = getattr(found, frag)
- return found
- except ImportError:
- e, tb = sys.exc_info()[1:]
- v = ValueError('Cannot resolve %r: %s' % (s, e))
- v.__cause__, v.__traceback__ = e, tb
- raise v
-
- def ext_convert(self, value):
- """Default converter for the ext:// protocol."""
- return self.resolve(value)
-
- def cfg_convert(self, value):
- """Default converter for the cfg:// protocol."""
- rest = value
- m = self.WORD_PATTERN.match(rest)
- if m is None:
- raise ValueError("Unable to convert %r" % value)
- else:
- rest = rest[m.end():]
- d = self.config[m.groups()[0]]
- #print d, rest
- while rest:
- m = self.DOT_PATTERN.match(rest)
- if m:
- d = d[m.groups()[0]]
- else:
- m = self.INDEX_PATTERN.match(rest)
- if m:
- idx = m.groups()[0]
- if not self.DIGIT_PATTERN.match(idx):
- d = d[idx]
- else:
- try:
- n = int(idx) # try as number first
- d = d[n]
- except TypeError:
- d = d[idx]
- if m:
- rest = rest[m.end():]
- else:
- raise ValueError('Unable to convert '
- '%r at %r' % (value, rest))
- #rest should be empty
- return d
-
- def convert(self, value):
- """
- Convert values to an appropriate type. dicts, lists and tuples are
- replaced by their converting alternatives. Strings are checked to
- see if they have a conversion format and are converted if they do.
- """
- if not isinstance(value, ConvertingDict) and isinstance(value, dict):
- value = ConvertingDict(value)
- value.configurator = self
- elif not isinstance(value, ConvertingList) and isinstance(value, list):
- value = ConvertingList(value)
- value.configurator = self
- elif not isinstance(value, ConvertingTuple) and\
- isinstance(value, tuple):
- value = ConvertingTuple(value)
- value.configurator = self
- elif isinstance(value, basestring): # str for py3k
- m = self.CONVERT_PATTERN.match(value)
- if m:
- d = m.groupdict()
- prefix = d['prefix']
- converter = self.value_converters.get(prefix, None)
- if converter:
- suffix = d['suffix']
- converter = getattr(self, converter)
- value = converter(suffix)
- return value
-
- def configure_custom(self, config):
- """Configure an object with a user-supplied factory."""
- c = config.pop('()')
- if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and \
- type(c) != types.ClassType:
- c = self.resolve(c)
- props = config.pop('.', None)
- # Check for valid identifiers
- kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
- result = c(**kwargs)
- if props:
- for name, value in props.items():
- setattr(result, name, value)
- return result
-
- def as_tuple(self, value):
- """Utility function which converts lists to tuples."""
- if isinstance(value, list):
- value = tuple(value)
- return value
-
-
-class DictConfigurator(BaseConfigurator):
- """
- Configure logging using a dictionary-like object to describe the
- configuration.
- """
-
- def configure(self):
- """Do the configuration."""
-
- config = self.config
- if 'version' not in config:
- raise ValueError("dictionary doesn't specify a version")
- if config['version'] != 1:
- raise ValueError("Unsupported version: %s" % config['version'])
- incremental = config.pop('incremental', False)
- EMPTY_DICT = {}
- logging._acquireLock()
- try:
- if incremental:
- handlers = config.get('handlers', EMPTY_DICT)
- # incremental handler config only if handler name
- # ties in to logging._handlers (Python 2.7)
- if sys.version_info[:2] == (2, 7):
- for name in handlers:
- if name not in logging._handlers:
- raise ValueError('No handler found with '
- 'name %r' % name)
- else:
- try:
- handler = logging._handlers[name]
- handler_config = handlers[name]
- level = handler_config.get('level', None)
- if level:
- handler.setLevel(_checkLevel(level))
- except StandardError, e:
- raise ValueError('Unable to configure handler '
- '%r: %s' % (name, e))
- loggers = config.get('loggers', EMPTY_DICT)
- for name in loggers:
- try:
- self.configure_logger(name, loggers[name], True)
- except StandardError, e:
- raise ValueError('Unable to configure logger '
- '%r: %s' % (name, e))
- root = config.get('root', None)
- if root:
- try:
- self.configure_root(root, True)
- except StandardError, e:
- raise ValueError('Unable to configure root '
- 'logger: %s' % e)
- else:
- disable_existing = config.pop('disable_existing_loggers', True)
-
- logging._handlers.clear()
- del logging._handlerList[:]
-
- # Do formatters first - they don't refer to anything else
- formatters = config.get('formatters', EMPTY_DICT)
- for name in formatters:
- try:
- formatters[name] = self.configure_formatter(
- formatters[name])
- except StandardError, e:
- raise ValueError('Unable to configure '
- 'formatter %r: %s' % (name, e))
- # Next, do filters - they don't refer to anything else, either
- filters = config.get('filters', EMPTY_DICT)
- for name in filters:
- try:
- filters[name] = self.configure_filter(filters[name])
- except StandardError, e:
- raise ValueError('Unable to configure '
- 'filter %r: %s' % (name, e))
-
- # Next, do handlers - they refer to formatters and filters
- # As handlers can refer to other handlers, sort the keys
- # to allow a deterministic order of configuration
- handlers = config.get('handlers', EMPTY_DICT)
- for name in sorted(handlers):
- try:
- handler = self.configure_handler(handlers[name])
- handler.name = name
- handlers[name] = handler
- except StandardError, e:
- raise ValueError('Unable to configure handler '
- '%r: %s' % (name, e))
- # Next, do loggers - they refer to handlers and filters
-
- #we don't want to lose the existing loggers,
- #since other threads may have pointers to them.
- #existing is set to contain all existing loggers,
- #and as we go through the new configuration we
- #remove any which are configured. At the end,
- #what's left in existing is the set of loggers
- #which were in the previous configuration but
- #which are not in the new configuration.
- root = logging.root
- existing = root.manager.loggerDict.keys()
- #The list needs to be sorted so that we can
- #avoid disabling child loggers of explicitly
- #named loggers. With a sorted list it is easier
- #to find the child loggers.
- existing.sort()
- #We'll keep the list of existing loggers
- #which are children of named loggers here...
- child_loggers = []
- #now set up the new ones...
- loggers = config.get('loggers', EMPTY_DICT)
- for name in loggers:
- if name in existing:
- i = existing.index(name)
- prefixed = name + "."
- pflen = len(prefixed)
- num_existing = len(existing)
- i = i + 1 # look at the entry after name
- while (i < num_existing) and\
- (existing[i][:pflen] == prefixed):
- child_loggers.append(existing[i])
- i = i + 1
- existing.remove(name)
- try:
- self.configure_logger(name, loggers[name])
- except StandardError, e:
- raise ValueError('Unable to configure logger '
- '%r: %s' % (name, e))
-
- #Disable any old loggers. There's no point deleting
- #them as other threads may continue to hold references
- #and by disabling them, you stop them doing any logging.
- #However, don't disable children of named loggers, as that's
- #probably not what was intended by the user.
- for log in existing:
- logger = root.manager.loggerDict[log]
- if log in child_loggers:
- logger.level = logging.NOTSET
- logger.handlers = []
- logger.propagate = True
- elif disable_existing:
- logger.disabled = True
-
- # And finally, do the root logger
- root = config.get('root', None)
- if root:
- try:
- self.configure_root(root)
- except StandardError, e:
- raise ValueError('Unable to configure root '
- 'logger: %s' % e)
- finally:
- logging._releaseLock()
-
- def configure_formatter(self, config):
- """Configure a formatter from a dictionary."""
- if '()' in config:
- factory = config['()'] # for use in exception handler
- try:
- result = self.configure_custom(config)
- except TypeError, te:
- if "'format'" not in str(te):
- raise
- #Name of parameter changed from fmt to format.
- #Retry with old name.
- #This is so that code can be used with older Python versions
- #(e.g. by Django)
- config['fmt'] = config.pop('format')
- config['()'] = factory
- result = self.configure_custom(config)
- else:
- fmt = config.get('format', None)
- dfmt = config.get('datefmt', None)
- result = logging.Formatter(fmt, dfmt)
- return result
-
- def configure_filter(self, config):
- """Configure a filter from a dictionary."""
- if '()' in config:
- result = self.configure_custom(config)
- else:
- name = config.get('name', '')
- result = logging.Filter(name)
- return result
-
- def add_filters(self, filterer, filters):
- """Add filters to a filterer from a list of names."""
- for f in filters:
- try:
- filterer.addFilter(self.config['filters'][f])
- except StandardError, e:
- raise ValueError('Unable to add filter %r: %s' % (f, e))
-
- def configure_handler(self, config):
- """Configure a handler from a dictionary."""
- formatter = config.pop('formatter', None)
- if formatter:
- try:
- formatter = self.config['formatters'][formatter]
- except StandardError, e:
- raise ValueError('Unable to set formatter '
- '%r: %s' % (formatter, e))
- level = config.pop('level', None)
- filters = config.pop('filters', None)
- if '()' in config:
- c = config.pop('()')
- if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and \
- type(c) != types.ClassType:
- c = self.resolve(c)
- factory = c
- else:
- klass = self.resolve(config.pop('class'))
- #Special case for handler which refers to another handler
- if issubclass(klass, logging.handlers.MemoryHandler) and\
- 'target' in config:
- try:
- config['target'] = self.config['handlers'][
- config['target']
- ]
- except StandardError, e:
- raise ValueError('Unable to set target handler '
- '%r: %s' % (config['target'], e))
- elif issubclass(klass, logging.handlers.SMTPHandler) and\
- 'mailhost' in config:
- config['mailhost'] = self.as_tuple(config['mailhost'])
- elif issubclass(klass, logging.handlers.SysLogHandler) and\
- 'address' in config:
- config['address'] = self.as_tuple(config['address'])
- factory = klass
- kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
- try:
- result = factory(**kwargs)
- except TypeError, te:
- if "'stream'" not in str(te):
- raise
- #The argument name changed from strm to stream
- #Retry with old name.
- #This is so that code can be used with older Python versions
- #(e.g. by Django)
- kwargs['strm'] = kwargs.pop('stream')
- result = factory(**kwargs)
- if formatter:
- result.setFormatter(formatter)
- if level is not None:
- result.setLevel(_checkLevel(level))
- if filters:
- self.add_filters(result, filters)
- return result
-
- def add_handlers(self, logger, handlers):
- """Add handlers to a logger from a list of names."""
- for h in handlers:
- try:
- logger.addHandler(self.config['handlers'][h])
- except StandardError, e:
- raise ValueError('Unable to add handler %r: %s' % (h, e))
-
- def common_logger_config(self, logger, config, incremental=False):
- """
- Perform configuration which is common to root and non-root loggers.
- """
- level = config.get('level', None)
- if level is not None:
- logger.setLevel(_checkLevel(level))
- if not incremental:
- #Remove any existing handlers
- for h in logger.handlers[:]:
- logger.removeHandler(h)
- handlers = config.get('handlers', None)
- if handlers:
- self.add_handlers(logger, handlers)
- filters = config.get('filters', None)
- if filters:
- self.add_filters(logger, filters)
-
- def configure_logger(self, name, config, incremental=False):
- """Configure a non-root logger from a dictionary."""
- logger = logging.getLogger(name)
- self.common_logger_config(logger, config, incremental)
- propagate = config.get('propagate', None)
- if propagate is not None:
- logger.propagate = propagate
-
- def configure_root(self, config, incremental=False):
- """Configure a root logger from a dictionary."""
- root = logging.getLogger()
- self.common_logger_config(root, config, incremental)
-
-dictConfigClass = DictConfigurator
-
-
-def dictConfig(config):
- """Configure logging using a dictionary."""
- dictConfigClass(config).configure()
diff --git a/pecan/configuration.py b/pecan/configuration.py
index 939f079..b4109a8 100644
--- a/pecan/configuration.py
+++ b/pecan/configuration.py
@@ -2,6 +2,8 @@ import re
import inspect
import os
+import six
+
IDENTIFIER = re.compile(r'[a-z_](\w)*$', re.IGNORECASE)
@@ -56,7 +58,7 @@ class Config(object):
'''
if isinstance(conf_dict, dict):
- iterator = conf_dict.iteritems()
+ iterator = six.iteritems(conf_dict)
else:
iterator = iter(conf_dict)
@@ -81,7 +83,7 @@ class Config(object):
'''
Private helper method for to_dict.
'''
- for k, v in obj.items():
+ for k, v in obj.copy().items():
if prefix:
del obj[k]
k = "%s%s" % (prefix, k)
@@ -118,21 +120,21 @@ class Config(object):
self.__values__[key] = ConfigDict(value)
else:
self.__values__[key] = Config(value, filename=self.__file__)
- elif isinstance(value, basestring) and '%(confdir)s' in value:
+ elif isinstance(value, six.string_types) and '%(confdir)s' in value:
confdir = os.path.dirname(self.__file__) or os.getcwd()
self.__values__[key] = value.replace('%(confdir)s', confdir)
else:
self.__values__[key] = value
def __iter__(self):
- return self.__values__.iteritems()
+ return six.iteritems(self.__values__)
def __dir__(self):
"""
When using dir() returns a list of the values in the config. Note:
This function only works in Python2.6 or later.
"""
- return self.__values__.keys()
+ return list(self.__values__.keys())
def __repr__(self):
return 'Config(%s)' % str(self.__values__)
@@ -150,7 +152,8 @@ def conf_from_file(filepath):
if not os.path.isfile(abspath):
raise RuntimeError('`%s` is not a file.' % abspath)
- execfile(abspath, globals(), conf_dict)
+ with open(abspath, 'rb') as f:
+ exec(compile(f.read(), abspath, 'exec'), globals(), conf_dict)
conf_dict['__file__'] = abspath
return conf_from_dict(conf_dict)
@@ -182,7 +185,7 @@ def conf_from_dict(conf_dict):
'''
conf = Config(filename=conf_dict.get('__file__', ''))
- for k, v in conf_dict.iteritems():
+ for k, v in six.iteritems(conf_dict):
if k.startswith('__'):
continue
elif inspect.ismodule(v):
@@ -215,7 +218,7 @@ def set_config(config, overwrite=False):
if overwrite is True:
_runtime_conf.empty()
- if isinstance(config, basestring):
+ if isinstance(config, six.string_types):
config = conf_from_file(config)
_runtime_conf.update(config)
if config.__file__:
diff --git a/pecan/core.py b/pecan/core.py
index 46b160a..c09763d 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -1,4 +1,3 @@
-import urllib
try:
from simplejson import loads
except ImportError: # pragma: no cover
@@ -6,17 +5,19 @@ except ImportError: # pragma: no cover
from threading import local
from itertools import chain
from mimetypes import guess_type, add_type
-from urlparse import urlsplit, urlunsplit
from os.path import splitext
import logging
import operator
+import six
+
from webob import Request, Response, exc, acceptparse
-from templating import RendererFactory
-from routing import lookup_controller, NonCanonicalPath
-from util import _cfg, encode_if_needed
-from middleware.recursive import ForwardRequestException
+from .compat import urlparse, unquote_plus
+from .templating import RendererFactory
+from .routing import lookup_controller, NonCanonicalPath
+from .util import _cfg, encode_if_needed
+from .middleware.recursive import ForwardRequestException
# make sure that json is defined in mimetypes
@@ -102,16 +103,16 @@ def redirect(location=None, internal=False, code=None, headers={},
if add_slash:
if location is None:
- split_url = list(urlsplit(state.request.url))
+ split_url = list(urlparse.urlsplit(state.request.url))
new_proto = state.request.environ.get(
'HTTP_X_FORWARDED_PROTO', split_url[0]
)
split_url[0] = new_proto
else:
- split_url = urlsplit(location)
+ split_url = urlparse.urlsplit(location)
split_url[2] = split_url[2].rstrip('/') + '/'
- location = urlunsplit(split_url)
+ location = urlparse.urlunsplit(split_url)
if not headers:
headers = {}
@@ -149,7 +150,7 @@ def load_app(config):
returns a pecan.Pecan object
'''
- from configuration import _runtime_conf, set_config
+ from .configuration import _runtime_conf, set_config
set_config(config, overwrite=True)
for package_name in getattr(_runtime_conf.app, 'modules', []):
@@ -198,7 +199,7 @@ class Pecan(object):
extra_template_vars={}, force_canonical=True,
guess_content_type_from_ext=True):
- if isinstance(root, basestring):
+ if isinstance(root, six.string_types):
root = self.__translate_root__(root)
self.root = root
@@ -248,7 +249,7 @@ class Pecan(object):
try:
node, remainder = lookup_controller(node, path)
return node, remainder
- except NonCanonicalPath, e:
+ except NonCanonicalPath as e:
if self.force_canonical and \
not _cfg(e.controller).get('accept_noncanonical', False):
if req.method == 'POST':
@@ -310,7 +311,8 @@ class Pecan(object):
valid_args = argspec[0][1:]
def _decode(x):
- return urllib.unquote_plus(x) if isinstance(x, basestring) else x
+ return unquote_plus(x) if isinstance(x, six.string_types) \
+ else x
remainder = [_decode(x) for x in remainder]
@@ -329,7 +331,7 @@ class Pecan(object):
valid_args = valid_args[len(args):]
# handle wildcard arguments
- if filter(None, remainder):
+ if [i for i in remainder if i]:
if not argspec[1]:
abort(404)
args.extend(remainder)
@@ -351,7 +353,7 @@ class Pecan(object):
# handle wildcard GET/POST params
if argspec[2]:
- for name, value in all_params.iteritems():
+ for name, value in six.iteritems(all_params):
if name not in argspec[0]:
kwargs[encode_if_needed(name)] = value
@@ -415,7 +417,7 @@ class Pecan(object):
# handle generic controllers
im_self = None
if cfg.get('generic'):
- im_self = controller.im_self
+ im_self = six.get_method_self(controller)
handlers = cfg['generic_handlers']
controller = handlers.get(req.method, handlers['DEFAULT'])
cfg = _cfg(controller)
@@ -526,8 +528,8 @@ class Pecan(object):
testing_variables['controller_output'] = result
# set the body content
- if isinstance(result, unicode):
- resp.unicode_body = result
+ if isinstance(result, six.text_type):
+ resp.text = result
else:
resp.body = result
@@ -555,7 +557,7 @@ class Pecan(object):
state.request.pecan = dict(content_type=None)
self.handle_request(state.request, state.response)
- except Exception, e:
+ except Exception as e:
# if this is an HTTP Exception, set it as the response
if isinstance(e, exc.HTTPException):
state.response = e
diff --git a/pecan/decorators.py b/pecan/decorators.py
index be27df8..2938fc1 100644
--- a/pecan/decorators.py
+++ b/pecan/decorators.py
@@ -1,5 +1,8 @@
-from inspect import getargspec, getmembers, isclass, ismethod
-from util import _cfg
+from inspect import getargspec, getmembers, isclass, ismethod, isfunction
+
+import six
+
+from .util import _cfg
__all__ = [
'expose', 'transactional', 'accept_noncanonical', 'after_commit',
@@ -74,7 +77,10 @@ def transactional(ignore_redirects=True):
def deco(f):
if isclass(f):
- for meth in [m[1] for m in getmembers(f) if ismethod(m[1])]:
+ for meth in [
+ m[1] for m in getmembers(f)
+ if (isfunction if six.PY3 else ismethod)(m[1])
+ ]:
if getattr(meth, 'exposed', False):
_cfg(meth)['transactional'] = True
_cfg(meth)['transactional_ignore_redirects'] = _cfg(
diff --git a/pecan/deploy.py b/pecan/deploy.py
index 08019f9..be6b359 100644
--- a/pecan/deploy.py
+++ b/pecan/deploy.py
@@ -1,4 +1,4 @@
-from core import load_app
+from .core import load_app
def deploy(config):
diff --git a/pecan/hooks.py b/pecan/hooks.py
index f7422b8..99ef27a 100644
--- a/pecan/hooks.py
+++ b/pecan/hooks.py
@@ -3,8 +3,8 @@ from inspect import getmembers
from webob.exc import HTTPFound
-from util import iscontroller, _cfg
-from routing import lookup_controller
+from .util import iscontroller, _cfg
+from .routing import lookup_controller
__all__ = [
'PecanHook', 'TransactionHook', 'HookController',
@@ -29,18 +29,27 @@ def walk_controller(root_class, controller, hooks):
walk_controller(root_class, value, hooks)
-class HookController(object):
+class HookControllerMeta(type):
'''
A base class for controllers that would like to specify hooks on
their controller methods. Simply create a list of hook objects
called ``__hooks__`` as a member of the controller's namespace.
'''
- __hooks__ = []
+ def __init__(cls, name, bases, dict_):
+ walk_controller(cls, cls, dict_.get('__hooks__', []))
- class __metaclass__(type):
- def __init__(cls, name, bases, dict_):
- walk_controller(cls, cls, dict_['__hooks__'])
+
+'''
+A base class for controllers that would like to specify hooks on
+their controller methods. Simply create a list of hook objects
+called ``__hooks__`` as a member of the controller's namespace.
+'''
+HookController = HookControllerMeta(
+ 'HookController',
+ (object,),
+ {}
+)
class PecanHook(object):
@@ -304,7 +313,7 @@ class RequestViewerHook(PecanHook):
value = getattr(state.request, request_info)
else:
value = value(self, state)
- except Exception, e:
+ except Exception as e:
value = e
terminal.append('%-12s - %s\n' % (request_info, value))
diff --git a/pecan/jsonify.py b/pecan/jsonify.py
index 7fbd492..2d89c53 100644
--- a/pecan/jsonify.py
+++ b/pecan/jsonify.py
@@ -16,6 +16,7 @@ except ImportError: # pragma no cover
from webob.multidict import MultiDict
webob_dicts = (MultiDict,)
+import six
from simplegeneric import generic
try:
@@ -66,8 +67,8 @@ class GenericJSON(JSONEncoder):
the entire resultset data, returns the list in a dictionary
along with the resultset "row" count.
- .. note:: {'count': 5, 'rows': [(u'Ed Jones',), (u'Pete Jones',),
- (u'Wendy Williams',), (u'Mary Contrary',), (u'Fred Smith',)]}
+ .. note:: {'count': 5, 'rows': [('Ed Jones',), ('Pete Jones',),
+ ('Wendy Williams',), ('Mary Contrary',), ('Fred Smith',)]}
* SQLAlchemy RowProxy objects
Casts the RowProxy cursor object into a dictionary, probably
@@ -77,7 +78,7 @@ class GenericJSON(JSONEncoder):
returns webob_dicts.mixed() dictionary, which is guaranteed
to be JSON-friendly.
'''
- if hasattr(obj, '__json__') and callable(obj.__json__):
+ if hasattr(obj, '__json__') and six.callable(obj.__json__):
return obj.__json__()
elif isinstance(obj, (date, datetime)):
return str(obj)
diff --git a/pecan/middleware/debug.py b/pecan/middleware/debug.py
index f6d3e53..0203245 100644
--- a/pecan/middleware/debug.py
+++ b/pecan/middleware/debug.py
@@ -1,8 +1,9 @@
-from cStringIO import StringIO
from traceback import print_exc
from pprint import pformat
import pdb
+from six.moves import cStringIO as StringIO
+
from mako.template import Template
from webob import Response
diff --git a/pecan/middleware/errordocument.py b/pecan/middleware/errordocument.py
index a4a7021..f08d536 100644
--- a/pecan/middleware/errordocument.py
+++ b/pecan/middleware/errordocument.py
@@ -1,5 +1,7 @@
import sys
-from recursive import ForwardRequestException, RecursionLoop
+
+from six import b as b_
+from .recursive import ForwardRequestException, RecursionLoop
class StatusPersist(object):
@@ -21,8 +23,8 @@ class StatusPersist(object):
try:
return self.app(environ, keep_status_start_response)
- except RecursionLoop, e:
- environ['wsgi.errors'].write(
+ except RecursionLoop as e:
+ environ['wsgi.errors'].errors.write(
'Recursion error getting error page: %s\n' % e
)
keep_status_start_response(
@@ -30,9 +32,9 @@ class StatusPersist(object):
[('Content-type', 'text/plain')],
sys.exc_info()
)
- return [
+ return [b_(
'Error: %s. (Error page could not be fetched)' % self.status
- ]
+ )]
class ErrorDocumentMiddleware(object):
diff --git a/pecan/middleware/recursive.py b/pecan/middleware/recursive.py
index f7471ec..b1296b8 100644
--- a/pecan/middleware/recursive.py
+++ b/pecan/middleware/recursive.py
@@ -54,7 +54,7 @@ class RecursiveMiddleware(object):
environ['pecan.recursive.script_name'] = my_script_name
try:
return self.application(environ, start_response)
- except ForwardRequestException, e:
+ except ForwardRequestException as e:
middleware = CheckForRecursionMiddleware(
e.factory(self), environ)
return middleware(environ, start_response)
diff --git a/pecan/middleware/resources/__init__.py b/pecan/middleware/resources/__init__.py
index 4c49785..2d8c627 100644
--- a/pecan/middleware/resources/__init__.py
+++ b/pecan/middleware/resources/__init__.py
@@ -1,7 +1,9 @@
import os
-import urllib
from mimetypes import guess_type
from contextlib import closing
+from base64 import b64encode
+
+from pecan.compat import quote
def load_resource(filename):
@@ -12,11 +14,10 @@ def load_resource(filename):
),
'rb'
)) as f:
+ data = f.read()
return 'data:%s;base64,%s' % (
guess_type(filename)[0],
- urllib.quote(
- f.read().encode('base64').replace('\n', '')
- )
+ quote(b64encode(data))
)
pecan_image = load_resource('pecan.png')
diff --git a/pecan/middleware/static.py b/pecan/middleware/static.py
index c68bce7..a05a7f8 100644
--- a/pecan/middleware/static.py
+++ b/pecan/middleware/static.py
@@ -10,6 +10,8 @@ import mimetypes
from datetime import datetime
from time import gmtime
+import six
+
class FileWrapper(object):
"""This class can be used to convert a :class:`file`-like object into
@@ -42,6 +44,10 @@ class FileWrapper(object):
raise StopIteration()
+if six.PY3:
+ FileWrapper.__next__ = FileWrapper.next
+
+
def wrap_file(environ, file, buffer_size=8192):
"""Wraps a file. This uses the WSGI server's file wrapper if available
or otherwise the generic :class:`FileWrapper`.
@@ -64,7 +70,7 @@ def _dump_date(d, delim):
d = gmtime()
elif isinstance(d, datetime):
d = d.utctimetuple()
- elif isinstance(d, (int, long, float)):
+ elif isinstance(d, (int, float)):
d = gmtime(d)
return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[d.tm_wday],
diff --git a/pecan/rest.py b/pecan/rest.py
index 542ccd5..ce111a8 100644
--- a/pecan/rest.py
+++ b/pecan/rest.py
@@ -2,10 +2,10 @@ from inspect import getargspec, ismethod
from webob import exc
-from core import abort, request
-from decorators import expose
-from routing import lookup_controller, handle_lookup_traversal
-from util import iscontroller
+from .core import abort, request
+from .decorators import expose
+from .routing import lookup_controller, handle_lookup_traversal
+from .util import iscontroller
class RestController(object):
diff --git a/pecan/routing.py b/pecan/routing.py
index a7a6d4f..31cbf92 100644
--- a/pecan/routing.py
+++ b/pecan/routing.py
@@ -2,8 +2,8 @@ import warnings
from webob import exc
-from secure import handle_security, cross_boundary
-from util import iscontroller
+from .secure import handle_security, cross_boundary
+from .util import iscontroller
__all__ = ['lookup_controller', 'find_object']
diff --git a/pecan/scaffolds/__init__.py b/pecan/scaffolds/__init__.py
index cd816da..ac22464 100644
--- a/pecan/scaffolds/__init__.py
+++ b/pecan/scaffolds/__init__.py
@@ -3,7 +3,8 @@ import os
import re
import pkg_resources
from string import Template
-from pecan.compat import native_, bytes_
+
+import six
DEFAULT_SCAFFOLD = 'base'
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
@@ -118,5 +119,20 @@ def render_template(content, variables):
input (content) and the variable names defined (vars).
"""
fsenc = sys.getfilesystemencoding()
- content = native_(content, fsenc)
- return bytes_(Template(content).substitute(variables), fsenc)
+
+ def to_native(s, encoding='latin-1', errors='strict'):
+ if six.PY3:
+ if isinstance(s, six.text_type):
+ return s
+ return str(s, encoding, errors)
+ else:
+ if isinstance(s, six.text_type):
+ return s.encode(encoding, errors)
+ return str(s)
+
+ output = Template(
+ to_native(content, fsenc)
+ ).substitute(variables)
+ if isinstance(output, six.text_type):
+ output = output.encode(fsenc, 'strict')
+ return output
diff --git a/pecan/scaffolds/base/+package+/model/__init__.py b/pecan/scaffolds/base/+package+/model/__init__.py
index bb1e2a9..2f1740f 100644
--- a/pecan/scaffolds/base/+package+/model/__init__.py
+++ b/pecan/scaffolds/base/+package+/model/__init__.py
@@ -1,4 +1,4 @@
-from pecan import conf
+from pecan import conf # noqa
def init_model():
diff --git a/pecan/secure.py b/pecan/secure.py
index c6f29cf..e44aacb 100644
--- a/pecan/secure.py
+++ b/pecan/secure.py
@@ -1,9 +1,16 @@
from functools import wraps
-from inspect import getmembers, ismethod, isfunction
+from inspect import getmembers, isfunction
from webob import exc
-from decorators import expose
-from util import _cfg, iscontroller
+import six
+
+if six.PY3:
+ from .compat import is_bound_method as ismethod
+else:
+ from inspect import ismethod
+
+from .decorators import expose
+from .util import _cfg, iscontroller
__all__ = ['unlocked', 'secure', 'SecureController']
@@ -19,6 +26,9 @@ class _SecureState(object):
def __nonzero__(self):
return self.boolean_value
+ def __bool__(self):
+ return self.__nonzero__()
+
Any = _SecureState('Any', False)
Protected = _SecureState('Protected', True)
@@ -56,7 +66,7 @@ class _SecuredAttribute(object):
self._parent = None
def _check_permissions(self):
- if isinstance(self.check_permissions, basestring):
+ if isinstance(self.check_permissions, six.string_types):
return getattr(self.parent, self.check_permissions)()
else:
return self.check_permissions()
@@ -66,7 +76,7 @@ class _SecuredAttribute(object):
def __set_parent(self, parent):
if ismethod(parent):
- self._parent = parent.im_self
+ self._parent = six.get_method_self(parent)
else:
self._parent = parent
parent = property(__get_parent, __set_parent)
@@ -82,7 +92,7 @@ def _allowed_check_permissions_types(x):
return (
ismethod(x) or
isfunction(x) or
- isinstance(x, basestring)
+ isinstance(x, six.string_types)
)
@@ -120,7 +130,7 @@ def secure(func_or_obj, check_permissions_for_obj=None):
return _SecuredAttribute(func_or_obj, check_permissions_for_obj)
-class SecureController(object):
+class SecureControllerMeta(type):
"""
Used to apply security to a controller.
Implementations of SecureController should extend the
@@ -128,48 +138,57 @@ class SecureController(object):
value (depending on whether or not the user has permissions
to the controller).
"""
- class __metaclass__(type):
- def __init__(cls, name, bases, dict_):
- cls._pecan = dict(
- secured=Protected,
- check_permissions=cls.check_permissions,
- unlocked=[]
- )
-
- for name, value in getmembers(cls)[:]:
- if ismethod(value):
- if iscontroller(value) and value._pecan.get(
- 'secured'
- ) is None:
- # Wrap the function so that the security context is
- # local to this class definition. This works around
- # the fact that unbound method attributes are shared
- # across classes with the same bases.
- wrapped = _make_wrapper(value)
- wrapped._pecan['secured'] = Protected
- wrapped._pecan['check_permissions'] = \
- cls.check_permissions
- setattr(cls, name, wrapped)
- elif hasattr(value, '__class__'):
- if name.startswith('__') and name.endswith('__'):
- continue
- if isinstance(value, _UnlockedAttribute):
- # mark it as unlocked and remove wrapper
- cls._pecan['unlocked'].append(value.obj)
- setattr(cls, name, value.obj)
- elif isinstance(value, _SecuredAttribute):
- # The user has specified a different check_permissions
- # than the class level version. As far as the class
- # is concerned, this method is unlocked because
- # it is using a check_permissions function embedded in
- # the _SecuredAttribute wrapper
- cls._pecan['unlocked'].append(value)
+ def __init__(cls, name, bases, dict_):
+ cls._pecan = dict(
+ secured=Protected,
+ check_permissions=cls.check_permissions,
+ unlocked=[]
+ )
+
+ for name, value in getmembers(cls)[:]:
+ if (isfunction if six.PY3 else ismethod)(value):
+ if iscontroller(value) and value._pecan.get(
+ 'secured'
+ ) is None:
+ # Wrap the function so that the security context is
+ # local to this class definition. This works around
+ # the fact that unbound method attributes are shared
+ # across classes with the same bases.
+ wrapped = _make_wrapper(value)
+ wrapped._pecan['secured'] = Protected
+ wrapped._pecan['check_permissions'] = \
+ cls.check_permissions
+ setattr(cls, name, wrapped)
+ elif hasattr(value, '__class__'):
+ if name.startswith('__') and name.endswith('__'):
+ continue
+ if isinstance(value, _UnlockedAttribute):
+ # mark it as unlocked and remove wrapper
+ cls._pecan['unlocked'].append(value.obj)
+ setattr(cls, name, value.obj)
+ elif isinstance(value, _SecuredAttribute):
+ # The user has specified a different check_permissions
+ # than the class level version. As far as the class
+ # is concerned, this method is unlocked because
+ # it is using a check_permissions function embedded in
+ # the _SecuredAttribute wrapper
+ cls._pecan['unlocked'].append(value)
+
+
+class SecureControllerBase(object):
@classmethod
def check_permissions(cls):
return False
+SecureController = SecureControllerMeta(
+ 'SecureController',
+ (SecureControllerBase,),
+ {}
+)
+
+
def _make_wrapper(f):
"""return a wrapped function with a copy of the _pecan context"""
@wraps(f)
@@ -185,8 +204,11 @@ def handle_security(controller):
if controller._pecan.get('secured', False):
check_permissions = controller._pecan['check_permissions']
- if isinstance(check_permissions, basestring):
- check_permissions = getattr(controller.im_self, check_permissions)
+ if isinstance(check_permissions, six.string_types):
+ check_permissions = getattr(
+ six.get_method_self(controller),
+ check_permissions
+ )
if not check_permissions():
raise exc.HTTPUnauthorized
diff --git a/pecan/templating.py b/pecan/templating.py
index 774cce0..81a60a0 100644
--- a/pecan/templating.py
+++ b/pecan/templating.py
@@ -1,5 +1,5 @@
-import cgi
-from jsonify import encode
+from .compat import escape
+from .jsonify import encode
_builtin_renderers = {}
error_formatters = []
@@ -57,7 +57,10 @@ try:
Implements ``Genshi`` renderer error formatting.
'''
if isinstance(exc_value, (gTemplateError)):
- retval = '<h4>Genshi error %s</h4>' % cgi.escape(exc_value.message)
+ retval = '<h4>Genshi error %s</h4>' % escape(
+ exc_value.args[0],
+ True
+ )
retval += format_line_context(exc_value.filename, exc_value.lineno)
return retval
error_formatters.append(format_genshi_error)
@@ -193,12 +196,12 @@ def format_line_context(filename, lineno, context=10):
start_lineno = max(lineno - context, 0)
end_lineno = lineno + context
- lines = [cgi.escape(l) for l in lines[start_lineno:end_lineno]]
+ lines = [escape(l, True) for l in lines[start_lineno:end_lineno]]
i = lineno - start_lineno
lines[i] = '<strong>%s</strong>' % lines[i]
else:
- lines = [cgi.escape(l) for l in lines[:context]]
+ lines = [escape(l, True) for l in lines[:context]]
msg = '<pre style="background-color:#ccc;padding:2em;">%s</pre>'
return msg % ''.join(lines)
diff --git a/pecan/tests/compat/__init__.py b/pecan/tests/compat/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/compat/__init__.py
+++ /dev/null
diff --git a/pecan/tests/compat/test_dictconfig.py b/pecan/tests/compat/test_dictconfig.py
deleted file mode 100644
index 7c31474..0000000
--- a/pecan/tests/compat/test_dictconfig.py
+++ /dev/null
@@ -1,733 +0,0 @@
-# Copyright 2009-2010 by Vinay Sajip. All Rights Reserved.
-#
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted,
-# provided that the above copyright notice appear in all copies and that
-# both that copyright notice and this permission notice appear in
-# supporting documentation, and that the name of Vinay Sajip
-# not be used in advertising or publicity pertaining to distribution
-# of the software without specific, written prior permission.
-# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
-# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import sys
-try:
- from cStringIO import StringIO
-except ImportError:
- from io import StringIO # noqa
-
-from pecan.compat.dictconfig import dictConfig
-import logging
-import re
-from test.test_support import captured_stdout
-import unittest
-
-
-class BaseTest(unittest.TestCase):
-
- """Base class for logging tests."""
-
- log_format = "%(name)s -> %(levelname)s: %(message)s"
- expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
- message_num = 0
-
- def setUp(self):
- """Setup the default logging stream to an internal StringIO instance,
- so that we can examine log output as we want."""
- logger_dict = logging.getLogger().manager.loggerDict
- logging._acquireLock()
- try:
- self.saved_handlers = logging._handlers.copy()
- self.saved_handler_list = logging._handlerList[:]
- self.saved_loggers = logger_dict.copy()
- self.saved_level_names = logging._levelNames.copy()
- finally:
- logging._releaseLock()
-
- self.root_logger = logging.getLogger("")
- self.original_logging_level = self.root_logger.getEffectiveLevel()
-
- self.stream = StringIO()
- self.root_logger.setLevel(logging.DEBUG)
- self.root_hdlr = logging.StreamHandler(self.stream)
- self.root_formatter = logging.Formatter(self.log_format)
- self.root_hdlr.setFormatter(self.root_formatter)
- self.root_logger.addHandler(self.root_hdlr)
-
- def tearDown(self):
- """Remove our logging stream, and restore the original logging
- level."""
- self.stream.close()
- self.root_logger.removeHandler(self.root_hdlr)
- self.root_logger.setLevel(self.original_logging_level)
- logging._acquireLock()
- try:
- logging._levelNames.clear()
- logging._levelNames.update(self.saved_level_names)
- logging._handlers.clear()
- logging._handlers.update(self.saved_handlers)
- logging._handlerList[:] = self.saved_handler_list
- loggerDict = logging.getLogger().manager.loggerDict
- loggerDict.clear()
- loggerDict.update(self.saved_loggers)
- finally:
- logging._releaseLock()
-
- def assert_log_lines(self, expected_values, stream=None):
- """Match the collected log lines against the regular expression
- self.expected_log_pat, and compare the extracted group values to
- the expected_values list of tuples."""
- stream = stream or self.stream
- pat = re.compile(self.expected_log_pat)
- try:
- stream.reset()
- actual_lines = stream.readlines()
- except AttributeError:
- # StringIO.StringIO lacks a reset() method.
- actual_lines = stream.getvalue().splitlines()
- self.assertEquals(len(actual_lines), len(expected_values))
- for actual, expected in zip(actual_lines, expected_values):
- match = pat.search(actual)
- if not match:
- self.fail("Log line does not match expected pattern:\n" +
- actual)
- self.assertEquals(tuple(match.groups()), expected)
- s = stream.read()
- if s:
- self.fail("Remaining output at end of log stream:\n" + s)
-
- def next_message(self):
- """Generate a message consisting solely of an auto-incrementing
- integer."""
- self.message_num += 1
- return "%d" % self.message_num
-
-
-class ExceptionFormatter(logging.Formatter):
- """A special exception formatter."""
- def formatException(self, ei):
- return "Got a [%s]" % ei[0].__name__
-
-
-def formatFunc(format, datefmt=None):
- return logging.Formatter(format, datefmt)
-
-
-def handlerFunc():
- return logging.StreamHandler()
-
-
-class CustomHandler(logging.StreamHandler):
- pass
-
-
-class ConfigDictTest(BaseTest):
-
- """Reading logging config from a dictionary."""
-
- expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
-
- # config0 is a standard configuration.
- config0 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'root': {
- 'level': 'WARNING',
- 'handlers': ['hand1'],
- },
- }
-
- # config1 adds a little to the standard configuration.
- config1 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- # config2 has a subtle configuration error that should be reported
- config2 = {
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdbout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- #As config1 but with a misspelt level on a handler
- config2a = {
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NTOSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- #As config1 but with a misspelt level on a logger
- config2b = {
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WRANING',
- },
- }
-
- # config3 has a less subtle configuration error
- config3 = {
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'misspelled_name',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- # config4 specifies a custom formatter class to be loaded
- config4 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- '()': __name__ + '.ExceptionFormatter',
- 'format': '%(levelname)s:%(name)s:%(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'root': {
- 'level': 'NOTSET',
- 'handlers': ['hand1'],
- },
- }
-
- # As config4 but using an actual callable rather than a string
- config4a = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- '()': ExceptionFormatter,
- 'format': '%(levelname)s:%(name)s:%(message)s',
- },
- 'form2': {
- '()': __name__ + '.formatFunc',
- 'format': '%(levelname)s:%(name)s:%(message)s',
- },
- 'form3': {
- '()': formatFunc,
- 'format': '%(levelname)s:%(name)s:%(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- 'hand2': {
- '()': handlerFunc,
- },
- },
- 'root': {
- 'level': 'NOTSET',
- 'handlers': ['hand1'],
- },
- }
-
- # config5 specifies a custom handler class to be loaded
- config5 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': __name__ + '.CustomHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- # config6 specifies a custom handler class to be loaded
- # but has bad arguments
- config6 = {
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': __name__ + '.CustomHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- '9': 'invalid parameter name',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- #config 7 does not define compiler.parser but defines compiler.lexer
- #so compiler.parser should be disabled after applying it
- config7 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.lexer': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- config8 = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler': {
- 'level': 'DEBUG',
- 'handlers': ['hand1'],
- },
- 'compiler.lexer': {
- },
- },
- 'root': {
- 'level': 'WARNING',
- },
- }
-
- config9 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'WARNING',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'WARNING',
- 'handlers': ['hand1'],
- },
- },
- 'root': {
- 'level': 'NOTSET',
- },
- }
-
- config9a = {
- 'version': 1,
- 'incremental': True,
- 'handlers': {
- 'hand1': {
- 'level': 'WARNING',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'INFO',
- },
- },
- }
-
- config9b = {
- 'version': 1,
- 'incremental': True,
- 'handlers': {
- 'hand1': {
- 'level': 'INFO',
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'INFO',
- },
- },
- }
-
- #As config1 but with a filter added
- config10 = {
- 'version': 1,
- 'formatters': {
- 'form1': {
- 'format': '%(levelname)s ++ %(message)s',
- },
- },
- 'filters': {
- 'filt1': {
- 'name': 'compiler.parser',
- },
- },
- 'handlers': {
- 'hand1': {
- 'class': 'logging.StreamHandler',
- 'formatter': 'form1',
- 'level': 'NOTSET',
- 'stream': 'ext://sys.stdout',
- 'filters': ['filt1'],
- },
- },
- 'loggers': {
- 'compiler.parser': {
- 'level': 'DEBUG',
- 'filters': ['filt1'],
- },
- },
- 'root': {
- 'level': 'WARNING',
- 'handlers': ['hand1'],
- },
- }
-
- def apply_config(self, conf):
- dictConfig(conf)
-
- def test_config0_ok(self):
- # A simple config which overrides the default settings.
- with captured_stdout() as output:
- self.apply_config(self.config0)
- logger = logging.getLogger()
- # Won't output anything
- logger.info(self.next_message())
- # Outputs a message
- logger.error(self.next_message())
- self.assert_log_lines([
- ('ERROR', '2'),
- ], stream=output)
- # Original logger output is empty.
- self.assert_log_lines([])
-
- def test_config1_ok(self, config=config1):
- # A config defining a sub-parser as well.
- with captured_stdout() as output:
- self.apply_config(config)
- logger = logging.getLogger("compiler.parser")
- # Both will output a message
- logger.info(self.next_message())
- logger.error(self.next_message())
- self.assert_log_lines([
- ('INFO', '1'),
- ('ERROR', '2'),
- ], stream=output)
- # Original logger output is empty.
- self.assert_log_lines([])
-
- def test_config2_failure(self):
- # A simple config which overrides the default settings.
- self.assertRaises(StandardError, self.apply_config, self.config2)
-
- def test_config2a_failure(self):
- # A simple config which overrides the default settings.
- self.assertRaises(StandardError, self.apply_config, self.config2a)
-
- def test_config2b_failure(self):
- # A simple config which overrides the default settings.
- self.assertRaises(StandardError, self.apply_config, self.config2b)
-
- def test_config3_failure(self):
- # A simple config which overrides the default settings.
- self.assertRaises(StandardError, self.apply_config, self.config3)
-
- def test_config4_ok(self):
- # A config specifying a custom formatter class.
- with captured_stdout() as output:
- self.apply_config(self.config4)
- #logger = logging.getLogger()
- try:
- raise RuntimeError()
- except RuntimeError:
- logging.exception("just testing")
- sys.stdout.seek(0)
- expected = "ERROR:root:just testing\nGot a [RuntimeError]\n"
- self.assertEquals(output.getvalue(), expected)
- # Original logger output is empty
- self.assert_log_lines([])
-
- def test_config4a_ok(self):
- # A config specifying a custom formatter class.
- with captured_stdout() as output:
- self.apply_config(self.config4a)
- #logger = logging.getLogger()
- try:
- raise RuntimeError()
- except RuntimeError:
- logging.exception("just testing")
- sys.stdout.seek(0)
- expected = "ERROR:root:just testing\nGot a [RuntimeError]\n"
- self.assertEquals(output.getvalue(), expected)
- # Original logger output is empty
- self.assert_log_lines([])
-
- def test_config5_ok(self):
- self.test_config1_ok(config=self.config5)
-
- def test_config6_failure(self):
- self.assertRaises(StandardError, self.apply_config, self.config6)
-
- def test_config7_ok(self):
- with captured_stdout() as output:
- self.apply_config(self.config1)
- logger = logging.getLogger("compiler.parser")
- # Both will output a message
- logger.info(self.next_message())
- logger.error(self.next_message())
- self.assert_log_lines([
- ('INFO', '1'),
- ('ERROR', '2'),
- ], stream=output)
- # Original logger output is empty.
- self.assert_log_lines([])
- with captured_stdout() as output:
- self.apply_config(self.config7)
- logger = logging.getLogger("compiler.parser")
- self.assertTrue(logger.disabled)
- logger = logging.getLogger("compiler.lexer")
- # Both will output a message
- logger.info(self.next_message())
- logger.error(self.next_message())
- self.assert_log_lines([
- ('INFO', '3'),
- ('ERROR', '4'),
- ], stream=output)
- # Original logger output is empty.
- self.assert_log_lines([])
-
- #Same as test_config_7_ok but don't disable old loggers.
- def test_config_8_ok(self):
- with captured_stdout() as output:
- self.apply_config(self.config1)
- logger = logging.getLogger("compiler.parser")
- # Both will output a message
- logger.info(self.next_message())
- logger.error(self.next_message())
- self.assert_log_lines([
- ('INFO', '1'),
- ('ERROR', '2'),
- ], stream=output)
- # Original logger output is empty.
- self.assert_log_lines([])
- with captured_stdout() as output:
- self.apply_config(self.config8)
- logger = logging.getLogger("compiler.parser")
- self.assertFalse(logger.disabled)
- # Both will output a message
- logger.info(self.next_message())
- logger.error(self.next_message())
- logger = logging.getLogger("compiler.lexer")
- # Both will output a message
- logger.info(self.next_message())
- logger.error(self.next_message())
- self.assert_log_lines([
- ('INFO', '3'),
- ('ERROR', '4'),
- ('INFO', '5'),
- ('ERROR', '6'),
- ], stream=output)
- # Original logger output is empty.
- self.assert_log_lines([])
-
- def test_config_9_ok(self):
- with captured_stdout() as output:
- self.apply_config(self.config9)
- logger = logging.getLogger("compiler.parser")
- # Nothing will be output since both handler and logger are
- # set to WARNING
- logger.info(self.next_message())
- self.assert_log_lines([], stream=output)
- self.apply_config(self.config9a)
- # Nothing will be output since both handler is still set
- # to WARNING
- logger.info(self.next_message())
- self.assert_log_lines([], stream=output)
- self.apply_config(self.config9b)
- # Message should now be output
- logger.info(self.next_message())
- if sys.version_info[:2] == (2, 7):
- self.assert_log_lines([
- ('INFO', '3'),
- ], stream=output)
- else:
- self.assert_log_lines([], stream=output)
-
- def test_config_10_ok(self):
- with captured_stdout() as output:
- self.apply_config(self.config10)
- logger = logging.getLogger("compiler.parser")
- logger.warning(self.next_message())
- logger = logging.getLogger('compiler')
- #Not output, because filtered
- logger.warning(self.next_message())
- logger = logging.getLogger('compiler.lexer')
- #Not output, because filtered
- logger.warning(self.next_message())
- logger = logging.getLogger("compiler.parser.codegen")
- #Output, as not filtered
- logger.error(self.next_message())
- self.assert_log_lines([
- ('WARNING', '1'),
- ('ERROR', '4'),
- ], stream=output)
diff --git a/pecan/tests/middleware/test_debug.py b/pecan/tests/middleware/test_debug.py
index 5358333..de9d4cd 100644
--- a/pecan/tests/middleware/test_debug.py
+++ b/pecan/tests/middleware/test_debug.py
@@ -1,6 +1,7 @@
from wsgiref.util import setup_testing_defaults
from webtest import TestApp
+from six import b as b_
from pecan.middleware.debug import DebugMiddleware
from pecan.tests import PecanTestCase
@@ -25,7 +26,7 @@ class TestDebugMiddleware(PecanTestCase):
if environ['PATH_INFO'] == '/error':
assert 1 == 2
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['requested page returned']
+ return [b_('requested page returned')]
self.app = TestApp(StripPasteVar(DebugMiddleware(
conditional_error_app
)))
@@ -33,12 +34,12 @@ class TestDebugMiddleware(PecanTestCase):
def test_middleware_passes_through_when_no_exception_raised(self):
r = self.app.get('/')
assert r.status_int == 200
- assert r.body == 'requested page returned'
+ assert r.body == b_('requested page returned')
def test_middleware_gives_stack_trace_on_errors(self):
r = self.app.get('/error', expect_errors=True)
assert r.status_int == 400
- assert 'AssertionError' in r.body
+ assert b_('AssertionError') in r.body
def test_middleware_complains_in_multi_process_environment(self):
diff --git a/pecan/tests/middleware/test_errordocument.py b/pecan/tests/middleware/test_errordocument.py
index c4faa42..29f46e5 100644
--- a/pecan/tests/middleware/test_errordocument.py
+++ b/pecan/tests/middleware/test_errordocument.py
@@ -1,6 +1,7 @@
import json
from webtest import TestApp
+from six import b as b_
import pecan
from pecan.middleware.errordocument import ErrorDocumentMiddleware
@@ -16,7 +17,7 @@ def four_oh_four_app(environ, start_response):
body = "Error: %s" % code
if environ['QUERY_STRING']:
body += "\nQS: %s" % environ['QUERY_STRING']
- return [body]
+ return [b_(body)]
start_response("404 Not Found", [('Content-type', 'text/plain')])
return []
@@ -32,12 +33,12 @@ class TestErrorDocumentMiddleware(PecanTestCase):
def test_hit_error_page(self):
r = self.app.get('/error/404')
assert r.status_int == 200
- assert r.body == 'Error: 404'
+ assert r.body == b_('Error: 404')
def test_middleware_routes_to_404_message(self):
r = self.app.get('/', expect_errors=True)
assert r.status_int == 404
- assert r.body == 'Error: 404'
+ assert r.body == b_('Error: 404')
def test_error_endpoint_with_query_string(self):
app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
@@ -45,7 +46,7 @@ class TestErrorDocumentMiddleware(PecanTestCase):
)))
r = app.get('/', expect_errors=True)
assert r.status_int == 404
- assert r.body == 'Error: 404\nQS: foo=bar'
+ assert r.body == b_('Error: 404\nQS: foo=bar')
def test_error_with_recursion_loop(self):
app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
@@ -53,8 +54,9 @@ class TestErrorDocumentMiddleware(PecanTestCase):
)))
r = app.get('/', expect_errors=True)
assert r.status_int == 404
- assert r.body == ('Error: 404 Not Found. '
- '(Error page could not be fetched)')
+ assert r.body == b_(
+ 'Error: 404 Not Found. (Error page could not be fetched)'
+ )
def test_original_exception(self):
@@ -85,6 +87,6 @@ class TestErrorDocumentMiddleware(PecanTestCase):
r = app.get('/', expect_errors=405)
assert r.status_int == 405
- resp = json.loads(r.body)
+ resp = json.loads(r.body.decode())
assert resp['status'] == 405
assert resp['reason'] == 'You have to POST, dummy!'
diff --git a/pecan/tests/middleware/test_recursive.py b/pecan/tests/middleware/test_recursive.py
index 8cd213c..ed95d50 100644
--- a/pecan/tests/middleware/test_recursive.py
+++ b/pecan/tests/middleware/test_recursive.py
@@ -1,4 +1,5 @@
from webtest import TestApp
+from six import b as b_
from pecan.middleware.recursive import (RecursiveMiddleware,
ForwardRequestException)
@@ -7,16 +8,16 @@ from pecan.tests import PecanTestCase
def simple_app(environ, start_response):
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['requested page returned']
+ return [b_('requested page returned')]
def error_docs_app(environ, start_response):
if environ['PATH_INFO'] == '/not_found':
start_response("404 Not found", [('Content-type', 'text/plain')])
- return ['Not found']
+ return [b_('Not found')]
elif environ['PATH_INFO'] == '/error':
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Page not found']
+ return [b_('Page not found')]
elif environ['PATH_INFO'] == '/recurse':
raise ForwardRequestException('/recurse')
else:
@@ -49,7 +50,7 @@ def forward(app):
assert 'Page not found' in res
try:
res = app.get('/recurse')
- except AssertionError, e:
+ except AssertionError as e:
if str(e).startswith('Forwarding loop detected'):
pass
else:
@@ -126,7 +127,7 @@ class TestRecursiveMiddleware(PecanTestCase):
assert 'Page not found' in res
try:
res = app.get('/recurse')
- except AssertionError, e:
+ except AssertionError as e:
if str(e).startswith('Forwarding loop detected'):
pass
else:
diff --git a/pecan/tests/scaffold_builder.py b/pecan/tests/scaffold_builder.py
index f1ae50e..e00b6da 100644
--- a/pecan/tests/scaffold_builder.py
+++ b/pecan/tests/scaffold_builder.py
@@ -1,7 +1,6 @@
import os
import sys
import subprocess
-import urllib2
import time
@@ -10,6 +9,9 @@ if sys.version_info < (2, 7):
else:
import unittest # noqa
+from six import b as b_
+
+from pecan.compat import urlopen, URLError
from pecan.tests import PecanTestCase
@@ -55,10 +57,11 @@ if __name__ == '__main__':
)
try:
# ...and that it's serving (valid) content...
- resp = urllib2.urlopen('http://localhost:8080/')
+ resp = urlopen('http://localhost:8080/')
assert resp.getcode() == 200
- assert 'This is a sample Pecan project.' in resp.read()
- except urllib2.URLError:
+ assert 'This is a sample Pecan project.' in \
+ resp.read().decode()
+ except URLError:
pass
else:
break
@@ -81,11 +84,11 @@ if __name__ == '__main__':
self.poll(proc)
out, _ = proc.communicate(
- '{"model" : model, "conf" : conf, "app" : app}'
+ b_('{"model" : model, "conf" : conf, "app" : app}')
)
- assert 'testing123.model' in out, out
- assert 'Config(' in out, out
- assert 'webtest.app.TestApp' in out, out
+ assert 'testing123.model' in out.decode(), out
+ assert 'Config(' in out.decode(), out
+ assert 'webtest.app.TestApp' in out.decode(), out
try:
# just in case stdin doesn't close
@@ -108,10 +111,11 @@ if __name__ == '__main__':
)
try:
# ...and that it's serving (valid) content...
- resp = urllib2.urlopen('http://localhost:%d/' % port)
+ resp = urlopen('http://localhost:%d/' % port)
assert resp.getcode() == 200
- assert 'This is a sample Pecan project.' in resp.read()
- except urllib2.URLError:
+ assert 'This is a sample Pecan project.' in \
+ resp.read().decode()
+ except URLError:
pass
else:
break
diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py
index d4b4371..37751b7 100644
--- a/pecan/tests/test_base.py
+++ b/pecan/tests/test_base.py
@@ -7,6 +7,9 @@ else:
import unittest # pragma: nocover
from webtest import TestApp
+import six
+from six import b as b_
+from six.moves import cStringIO as StringIO
from pecan import (
Pecan, expose, request, response, redirect, abort, make_app,
@@ -44,17 +47,17 @@ class TestIndexRouting(PecanTestCase):
def test_empty_root(self):
r = self.app_.get('/')
assert r.status_int == 200
- assert r.body == 'Hello, World!'
+ assert r.body == b_('Hello, World!')
def test_index(self):
r = self.app_.get('/index')
assert r.status_int == 200
- assert r.body == 'Hello, World!'
+ assert r.body == b_('Hello, World!')
def test_index_html(self):
r = self.app_.get('/index.html')
assert r.status_int == 200
- assert r.body == 'Hello, World!'
+ assert r.body == b_('Hello, World!')
class TestObjectDispatch(PecanTestCase):
@@ -97,22 +100,22 @@ class TestObjectDispatch(PecanTestCase):
def test_index(self):
r = self.app_.get('/')
assert r.status_int == 200
- assert r.body == '/'
+ assert r.body == b_('/')
def test_one_level(self):
r = self.app_.get('/deeper')
assert r.status_int == 200
- assert r.body == '/deeper'
+ assert r.body == b_('/deeper')
def test_one_level_with_trailing(self):
r = self.app_.get('/sub/')
assert r.status_int == 200
- assert r.body == '/sub/'
+ assert r.body == b_('/sub/')
def test_two_levels(self):
r = self.app_.get('/sub/deeper')
assert r.status_int == 200
- assert r.body == '/sub/deeper'
+ assert r.body == b_('/sub/deeper')
def test_two_levels_with_trailing(self):
r = self.app_.get('/sub/sub/')
@@ -121,7 +124,7 @@ class TestObjectDispatch(PecanTestCase):
def test_three_levels(self):
r = self.app_.get('/sub/sub/deeper')
assert r.status_int == 200
- assert r.body == '/sub/sub/deeper'
+ assert r.body == b_('/sub/sub/deeper')
class TestLookups(PecanTestCase):
@@ -154,17 +157,17 @@ class TestLookups(PecanTestCase):
def test_index(self):
r = self.app_.get('/')
assert r.status_int == 200
- assert r.body == '/'
+ assert r.body == b_('/')
def test_lookup(self):
r = self.app_.get('/100/')
assert r.status_int == 200
- assert r.body == '/100'
+ assert r.body == b_('/100')
def test_lookup_with_method(self):
r = self.app_.get('/100/name')
assert r.status_int == 200
- assert r.body == '/100/name'
+ assert r.body == b_('/100/name')
def test_lookup_with_wrong_argspec(self):
class RootController(object):
@@ -245,19 +248,22 @@ class TestControllerArguments(PecanTestCase):
try:
r = self.app_.get('/')
assert r.status_int != 200 # pragma: nocover
- except Exception, ex:
+ except Exception as ex:
assert type(ex) == TypeError
- assert ex.args[0] == 'index() takes exactly 2 arguments (1 given)'
+ assert ex.args[0] in (
+ "index() takes exactly 2 arguments (1 given)",
+ "index() missing 1 required positional argument: 'id'"
+ ) # this messaging changed in Python 3.3
def test_single_argument(self):
r = self.app_.get('/1')
assert r.status_int == 200
- assert r.body == 'index: 1'
+ assert r.body == b_('index: 1')
def test_single_argument_with_encoded_url(self):
r = self.app_.get('/This%20is%20a%20test%21')
assert r.status_int == 200
- assert r.body == 'index: This is a test!'
+ assert r.body == b_('index: This is a test!')
def test_two_arguments(self):
r = self.app_.get('/1/dummy', status=404)
@@ -266,90 +272,90 @@ class TestControllerArguments(PecanTestCase):
def test_keyword_argument(self):
r = self.app_.get('/?id=2')
assert r.status_int == 200
- assert r.body == 'index: 2'
+ assert r.body == b_('index: 2')
def test_keyword_argument_with_encoded_url(self):
r = self.app_.get('/?id=This%20is%20a%20test%21')
assert r.status_int == 200
- assert r.body == 'index: This is a test!'
+ assert r.body == b_('index: This is a test!')
def test_argument_and_keyword_argument(self):
r = self.app_.get('/3?id=three')
assert r.status_int == 200
- assert r.body == 'index: 3'
+ assert r.body == b_('index: 3')
def test_encoded_argument_and_keyword_argument(self):
r = self.app_.get('/This%20is%20a%20test%21?id=three')
assert r.status_int == 200
- assert r.body == 'index: This is a test!'
+ assert r.body == b_('index: This is a test!')
def test_explicit_kwargs(self):
r = self.app_.post('/', {'id': '4'})
assert r.status_int == 200
- assert r.body == 'index: 4'
+ assert r.body == b_('index: 4')
def test_path_with_explicit_kwargs(self):
r = self.app_.post('/4', {'id': 'four'})
assert r.status_int == 200
- assert r.body == 'index: 4'
+ assert r.body == b_('index: 4')
def test_multiple_kwargs(self):
r = self.app_.get('/?id=5&dummy=dummy')
assert r.status_int == 200
- assert r.body == 'index: 5'
+ assert r.body == b_('index: 5')
def test_kwargs_from_root(self):
r = self.app_.post('/', {'id': '6', 'dummy': 'dummy'})
assert r.status_int == 200
- assert r.body == 'index: 6'
+ assert r.body == b_('index: 6')
# multiple args
def test_multiple_positional_arguments(self):
r = self.app_.get('/multiple/one/two')
assert r.status_int == 200
- assert r.body == 'multiple: one, two'
+ assert r.body == b_('multiple: one, two')
def test_multiple_positional_arguments_with_url_encode(self):
r = self.app_.get('/multiple/One%20/Two%21')
assert r.status_int == 200
- assert r.body == 'multiple: One , Two!'
+ assert r.body == b_('multiple: One , Two!')
def test_multiple_positional_arguments_with_kwargs(self):
r = self.app_.get('/multiple?one=three&two=four')
assert r.status_int == 200
- assert r.body == 'multiple: three, four'
+ assert r.body == b_('multiple: three, four')
def test_multiple_positional_arguments_with_url_encoded_kwargs(self):
r = self.app_.get('/multiple?one=Three%20&two=Four%20%21')
assert r.status_int == 200
- assert r.body == 'multiple: Three , Four !'
+ assert r.body == b_('multiple: Three , Four !')
def test_positional_args_with_dictionary_kwargs(self):
r = self.app_.post('/multiple', {'one': 'five', 'two': 'six'})
assert r.status_int == 200
- assert r.body == 'multiple: five, six'
+ assert r.body == b_('multiple: five, six')
def test_positional_args_with_url_encoded_dictionary_kwargs(self):
r = self.app_.post('/multiple', {'one': 'Five%20', 'two': 'Six%20%21'})
assert r.status_int == 200
- assert r.body == 'multiple: Five%20, Six%20%21'
+ assert r.body == b_('multiple: Five%20, Six%20%21')
# optional arg
def test_optional_arg(self):
r = self.app_.get('/optional')
assert r.status_int == 200
- assert r.body == 'optional: None'
+ assert r.body == b_('optional: None')
def test_multiple_optional(self):
r = self.app_.get('/optional/1')
assert r.status_int == 200
- assert r.body == 'optional: 1'
+ assert r.body == b_('optional: 1')
def test_multiple_optional_url_encoded(self):
r = self.app_.get('/optional/Some%20Number')
assert r.status_int == 200
- assert r.body == 'optional: Some Number'
+ assert r.body == b_('optional: Some Number')
def test_multiple_optional_missing(self):
r = self.app_.get('/optional/2/dummy', status=404)
@@ -358,57 +364,57 @@ class TestControllerArguments(PecanTestCase):
def test_multiple_with_kwargs(self):
r = self.app_.get('/optional?id=2')
assert r.status_int == 200
- assert r.body == 'optional: 2'
+ assert r.body == b_('optional: 2')
def test_multiple_with_url_encoded_kwargs(self):
r = self.app_.get('/optional?id=Some%20Number')
assert r.status_int == 200
- assert r.body == 'optional: Some Number'
+ assert r.body == b_('optional: Some Number')
def test_multiple_args_with_url_encoded_kwargs(self):
r = self.app_.get('/optional/3?id=three')
assert r.status_int == 200
- assert r.body == 'optional: 3'
+ assert r.body == b_('optional: 3')
def test_url_encoded_positional_args(self):
r = self.app_.get('/optional/Some%20Number?id=three')
assert r.status_int == 200
- assert r.body == 'optional: Some Number'
+ assert r.body == b_('optional: Some Number')
def test_optional_arg_with_kwargs(self):
r = self.app_.post('/optional', {'id': '4'})
assert r.status_int == 200
- assert r.body == 'optional: 4'
+ assert r.body == b_('optional: 4')
def test_optional_arg_with_url_encoded_kwargs(self):
r = self.app_.post('/optional', {'id': 'Some%20Number'})
assert r.status_int == 200
- assert r.body == 'optional: Some%20Number'
+ assert r.body == b_('optional: Some%20Number')
def test_multiple_positional_arguments_with_dictionary_kwargs(self):
r = self.app_.post('/optional/5', {'id': 'five'})
assert r.status_int == 200
- assert r.body == 'optional: 5'
+ assert r.body == b_('optional: 5')
def test_multiple_positional_url_encoded_arguments_with_kwargs(self):
r = self.app_.post('/optional/Some%20Number', {'id': 'five'})
assert r.status_int == 200
- assert r.body == 'optional: Some Number'
+ assert r.body == b_('optional: Some Number')
def test_optional_arg_with_multiple_kwargs(self):
r = self.app_.get('/optional?id=6&dummy=dummy')
assert r.status_int == 200
- assert r.body == 'optional: 6'
+ assert r.body == b_('optional: 6')
def test_optional_arg_with_multiple_url_encoded_kwargs(self):
r = self.app_.get('/optional?id=Some%20Number&dummy=dummy')
assert r.status_int == 200
- assert r.body == 'optional: Some Number'
+ assert r.body == b_('optional: Some Number')
def test_optional_arg_with_multiple_dictionary_kwargs(self):
r = self.app_.post('/optional', {'id': '7', 'dummy': 'dummy'})
assert r.status_int == 200
- assert r.body == 'optional: 7'
+ assert r.body == b_('optional: 7')
def test_optional_arg_with_multiple_url_encoded_dictionary_kwargs(self):
r = self.app_.post('/optional', {
@@ -416,34 +422,34 @@ class TestControllerArguments(PecanTestCase):
'dummy': 'dummy'
})
assert r.status_int == 200
- assert r.body == 'optional: Some%20Number'
+ assert r.body == b_('optional: Some%20Number')
# multiple optional args
def test_multiple_optional_positional_args(self):
r = self.app_.get('/multiple_optional')
assert r.status_int == 200
- assert r.body == 'multiple_optional: None, None, None'
+ assert r.body == b_('multiple_optional: None, None, None')
def test_multiple_optional_positional_args_one_arg(self):
r = self.app_.get('/multiple_optional/1')
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, None, None'
+ assert r.body == b_('multiple_optional: 1, None, None')
def test_multiple_optional_positional_args_one_url_encoded_arg(self):
r = self.app_.get('/multiple_optional/One%21')
assert r.status_int == 200
- assert r.body == 'multiple_optional: One!, None, None'
+ assert r.body == b_('multiple_optional: One!, None, None')
def test_multiple_optional_positional_args_all_args(self):
r = self.app_.get('/multiple_optional/1/2/3')
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, 2, 3'
+ assert r.body == b_('multiple_optional: 1, 2, 3')
def test_multiple_optional_positional_args_all_url_encoded_args(self):
r = self.app_.get('/multiple_optional/One%21/Two%21/Three%21')
assert r.status_int == 200
- assert r.body == 'multiple_optional: One!, Two!, Three!'
+ assert r.body == b_('multiple_optional: One!, Two!, Three!')
def test_multiple_optional_positional_args_too_many_args(self):
r = self.app_.get('/multiple_optional/1/2/3/dummy', status=404)
@@ -452,54 +458,54 @@ class TestControllerArguments(PecanTestCase):
def test_multiple_optional_positional_args_with_kwargs(self):
r = self.app_.get('/multiple_optional?one=1')
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, None, None'
+ assert r.body == b_('multiple_optional: 1, None, None')
def test_multiple_optional_positional_args_with_url_encoded_kwargs(self):
r = self.app_.get('/multiple_optional?one=One%21')
assert r.status_int == 200
- assert r.body == 'multiple_optional: One!, None, None'
+ assert r.body == b_('multiple_optional: One!, None, None')
def test_multiple_optional_positional_args_with_string_kwargs(self):
r = self.app_.get('/multiple_optional/1?one=one')
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, None, None'
+ assert r.body == b_('multiple_optional: 1, None, None')
def test_multiple_optional_positional_args_with_encoded_str_kwargs(self):
r = self.app_.get('/multiple_optional/One%21?one=one')
assert r.status_int == 200
- assert r.body == 'multiple_optional: One!, None, None'
+ assert r.body == b_('multiple_optional: One!, None, None')
def test_multiple_optional_positional_args_with_dict_kwargs(self):
r = self.app_.post('/multiple_optional', {'one': '1'})
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, None, None'
+ assert r.body == b_('multiple_optional: 1, None, None')
def test_multiple_optional_positional_args_with_encoded_dict_kwargs(self):
r = self.app_.post('/multiple_optional', {'one': 'One%21'})
assert r.status_int == 200
- assert r.body == 'multiple_optional: One%21, None, None'
+ assert r.body == b_('multiple_optional: One%21, None, None')
def test_multiple_optional_positional_args_and_dict_kwargs(self):
r = self.app_.post('/multiple_optional/1', {'one': 'one'})
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, None, None'
+ assert r.body == b_('multiple_optional: 1, None, None')
def test_multiple_optional_encoded_positional_args_and_dict_kwargs(self):
r = self.app_.post('/multiple_optional/One%21', {'one': 'one'})
assert r.status_int == 200
- assert r.body == 'multiple_optional: One!, None, None'
+ assert r.body == b_('multiple_optional: One!, None, None')
def test_multiple_optional_args_with_multiple_kwargs(self):
r = self.app_.get('/multiple_optional?one=1&two=2&three=3&four=4')
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, 2, 3'
+ assert r.body == b_('multiple_optional: 1, 2, 3')
def test_multiple_optional_args_with_multiple_encoded_kwargs(self):
r = self.app_.get(
'/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4'
)
assert r.status_int == 200
- assert r.body == 'multiple_optional: One!, Two!, Three!'
+ assert r.body == b_('multiple_optional: One!, Two!, Three!')
def test_multiple_optional_args_with_multiple_dict_kwargs(self):
r = self.app_.post(
@@ -507,7 +513,7 @@ class TestControllerArguments(PecanTestCase):
{'one': '1', 'two': '2', 'three': '3', 'four': '4'}
)
assert r.status_int == 200
- assert r.body == 'multiple_optional: 1, 2, 3'
+ assert r.body == b_('multiple_optional: 1, 2, 3')
def test_multiple_optional_args_with_multiple_encoded_dict_kwargs(self):
r = self.app_.post(
@@ -520,52 +526,52 @@ class TestControllerArguments(PecanTestCase):
}
)
assert r.status_int == 200
- assert r.body == 'multiple_optional: One%21, Two%21, Three%21'
+ assert r.body == b_('multiple_optional: One%21, Two%21, Three%21')
def test_multiple_optional_args_with_last_kwarg(self):
r = self.app_.get('/multiple_optional?three=3')
assert r.status_int == 200
- assert r.body == 'multiple_optional: None, None, 3'
+ assert r.body == b_('multiple_optional: None, None, 3')
def test_multiple_optional_args_with_last_encoded_kwarg(self):
r = self.app_.get('/multiple_optional?three=Three%21')
assert r.status_int == 200
- assert r.body == 'multiple_optional: None, None, Three!'
+ assert r.body == b_('multiple_optional: None, None, Three!')
def test_multiple_optional_args_with_middle_arg(self):
r = self.app_.get('/multiple_optional', {'two': '2'})
assert r.status_int == 200
- assert r.body == 'multiple_optional: None, 2, None'
+ assert r.body == b_('multiple_optional: None, 2, None')
def test_variable_args(self):
r = self.app_.get('/variable_args')
assert r.status_int == 200
- assert r.body == 'variable_args: '
+ assert r.body == b_('variable_args: ')
def test_multiple_variable_args(self):
r = self.app_.get('/variable_args/1/dummy')
assert r.status_int == 200
- assert r.body == 'variable_args: 1, dummy'
+ assert r.body == b_('variable_args: 1, dummy')
def test_multiple_encoded_variable_args(self):
r = self.app_.get('/variable_args/Testing%20One%20Two/Three%21')
assert r.status_int == 200
- assert r.body == 'variable_args: Testing One Two, Three!'
+ assert r.body == b_('variable_args: Testing One Two, Three!')
def test_variable_args_with_kwargs(self):
r = self.app_.get('/variable_args?id=2&dummy=dummy')
assert r.status_int == 200
- assert r.body == 'variable_args: '
+ assert r.body == b_('variable_args: ')
def test_variable_args_with_dict_kwargs(self):
r = self.app_.post('/variable_args', {'id': '3', 'dummy': 'dummy'})
assert r.status_int == 200
- assert r.body == 'variable_args: '
+ assert r.body == b_('variable_args: ')
def test_variable_kwargs(self):
r = self.app_.get('/variable_kwargs')
assert r.status_int == 200
- assert r.body == 'variable_kwargs: '
+ assert r.body == b_('variable_kwargs: ')
def test_multiple_variable_kwargs(self):
r = self.app_.get('/variable_kwargs/1/dummy', status=404)
@@ -574,19 +580,19 @@ class TestControllerArguments(PecanTestCase):
def test_multiple_variable_kwargs_with_explicit_kwargs(self):
r = self.app_.get('/variable_kwargs?id=2&dummy=dummy')
assert r.status_int == 200
- assert r.body == 'variable_kwargs: dummy=dummy, id=2'
+ assert r.body == b_('variable_kwargs: dummy=dummy, id=2')
def test_multiple_variable_kwargs_with_explicit_encoded_kwargs(self):
r = self.app_.get(
'/variable_kwargs?id=Two%21&dummy=This%20is%20a%20test'
)
assert r.status_int == 200
- assert r.body == 'variable_kwargs: dummy=This is a test, id=Two!'
+ assert r.body == b_('variable_kwargs: dummy=This is a test, id=Two!')
def test_multiple_variable_kwargs_with_dict_kwargs(self):
r = self.app_.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'})
assert r.status_int == 200
- assert r.body == 'variable_kwargs: dummy=dummy, id=3'
+ assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
def test_multiple_variable_kwargs_with_encoded_dict_kwargs(self):
r = self.app_.post(
@@ -595,42 +601,42 @@ class TestControllerArguments(PecanTestCase):
)
assert r.status_int == 200
result = 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21'
- assert r.body == result
+ assert r.body == b_(result)
def test_variable_all(self):
r = self.app_.get('/variable_all')
assert r.status_int == 200
- assert r.body == 'variable_all: '
+ assert r.body == b_('variable_all: ')
def test_variable_all_with_one_extra(self):
r = self.app_.get('/variable_all/1')
assert r.status_int == 200
- assert r.body == 'variable_all: 1'
+ assert r.body == b_('variable_all: 1')
def test_variable_all_with_two_extras(self):
r = self.app_.get('/variable_all/2/dummy')
assert r.status_int == 200
- assert r.body == 'variable_all: 2, dummy'
+ assert r.body == b_('variable_all: 2, dummy')
def test_variable_mixed(self):
r = self.app_.get('/variable_all/3?month=1&day=12')
assert r.status_int == 200
- assert r.body == 'variable_all: 3, day=12, month=1'
+ assert r.body == b_('variable_all: 3, day=12, month=1')
def test_variable_mixed_explicit(self):
r = self.app_.get('/variable_all/4?id=four&month=1&day=12')
assert r.status_int == 200
- assert r.body == 'variable_all: 4, day=12, id=four, month=1'
+ assert r.body == b_('variable_all: 4, day=12, id=four, month=1')
def test_variable_post(self):
r = self.app_.post('/variable_all/5/dummy')
assert r.status_int == 200
- assert r.body == 'variable_all: 5, dummy'
+ assert r.body == b_('variable_all: 5, dummy')
def test_variable_post_with_kwargs(self):
r = self.app_.post('/variable_all/6', {'month': '1', 'day': '12'})
assert r.status_int == 200
- assert r.body == 'variable_all: 6, day=12, month=1'
+ assert r.body == b_('variable_all: 6, day=12, month=1')
def test_variable_post_mixed(self):
r = self.app_.post(
@@ -638,60 +644,63 @@ class TestControllerArguments(PecanTestCase):
{'id': 'seven', 'month': '1', 'day': '12'}
)
assert r.status_int == 200
- assert r.body == 'variable_all: 7, day=12, id=seven, month=1'
+ assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
def test_no_remainder(self):
try:
r = self.app_.get('/eater')
assert r.status_int != 200 # pragma: nocover
- except Exception, ex:
+ except Exception as ex:
assert type(ex) == TypeError
- assert ex.args[0] == 'eater() takes at least 2 arguments (1 given)'
+ assert ex.args[0] in (
+ "eater() takes at least 2 arguments (1 given)",
+ "eater() missing 1 required positional argument: 'id'"
+ ) # this messaging changed in Python 3.3
def test_one_remainder(self):
r = self.app_.get('/eater/1')
assert r.status_int == 200
- assert r.body == 'eater: 1, None, '
+ assert r.body == b_('eater: 1, None, ')
def test_two_remainders(self):
r = self.app_.get('/eater/2/dummy')
assert r.status_int == 200
- assert r.body == 'eater: 2, dummy, '
+ assert r.body == b_('eater: 2, dummy, ')
def test_many_remainders(self):
r = self.app_.get('/eater/3/dummy/foo/bar')
assert r.status_int == 200
- assert r.body == 'eater: 3, dummy, foo, bar'
+ assert r.body == b_('eater: 3, dummy, foo, bar')
def test_remainder_with_kwargs(self):
r = self.app_.get('/eater/4?month=1&day=12')
assert r.status_int == 200
- assert r.body == 'eater: 4, None, day=12, month=1'
+ assert r.body == b_('eater: 4, None, day=12, month=1')
def test_remainder_with_many_kwargs(self):
r = self.app_.get('/eater/5?id=five&month=1&day=12&dummy=dummy')
assert r.status_int == 200
- assert r.body == 'eater: 5, dummy, day=12, month=1'
+ assert r.body == b_('eater: 5, dummy, day=12, month=1')
def test_post_remainder(self):
r = self.app_.post('/eater/6')
assert r.status_int == 200
- assert r.body == 'eater: 6, None, '
+ assert r.body == b_('eater: 6, None, ')
def test_post_three_remainders(self):
r = self.app_.post('/eater/7/dummy')
assert r.status_int == 200
- assert r.body == 'eater: 7, dummy, '
+ assert r.body == b_('eater: 7, dummy, ')
def test_post_many_remainders(self):
r = self.app_.post('/eater/8/dummy/foo/bar')
assert r.status_int == 200
- assert r.body == 'eater: 8, dummy, foo, bar'
+ assert r.body == b_('eater: 8, dummy, foo, bar')
def test_post_remainder_with_kwargs(self):
r = self.app_.post('/eater/9', {'month': '1', 'day': '12'})
assert r.status_int == 200
- assert r.body == 'eater: 9, None, day=12, month=1'
+ assert r.body == b_('eater: 9, None, day=12, month=1')
def test_post_many_remainders_with_many_kwargs(self):
r = self.app_.post(
@@ -699,7 +708,7 @@ class TestControllerArguments(PecanTestCase):
{'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
)
assert r.status_int == 200
- assert r.body == 'eater: 10, dummy, day=12, month=1'
+ assert r.body == b_('eater: 10, dummy, day=12, month=1')
class TestAbort(PecanTestCase):
@@ -774,12 +783,12 @@ class TestRedirect(PecanTestCase):
assert r.status_int == 302
r = r.follow()
assert r.status_int == 200
- assert r.body == 'it worked!'
+ assert r.body == b_('it worked!')
def test_internal(self):
r = self.app_.get('/internal')
assert r.status_int == 200
- assert r.body == 'it worked!'
+ assert r.body == b_('it worked!')
def test_internal_with_301(self):
self.assertRaises(ValueError, self.app_.get, '/bad_internal')
@@ -789,7 +798,7 @@ class TestRedirect(PecanTestCase):
assert r.status_int == 301
r = r.follow()
assert r.status_int == 200
- assert r.body == 'it worked!'
+ assert r.body == b_('it worked!')
def test_x_forward_proto(self):
class ChildController(object):
@@ -821,14 +830,13 @@ class TestRedirect(PecanTestCase):
class TestStreamedResponse(PecanTestCase):
def test_streaming_response(self):
- import StringIO
class RootController(object):
@expose(content_type='text/plain')
def test(self, foo):
if foo == 'stream':
# mimic large file
- contents = StringIO.StringIO('stream')
+ contents = six.BytesIO(b_('stream'))
response.content_type = 'application/octet-stream'
contents.seek(0, os.SEEK_END)
response.content_length = contents.tell()
@@ -841,11 +849,11 @@ class TestStreamedResponse(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/test/stream')
assert r.content_type == 'application/octet-stream'
- assert r.body == 'stream'
+ assert r.body == b_('stream')
r = app.get('/test/plain')
assert r.content_type == 'text/plain'
- assert r.body == 'plain text'
+ assert r.body == b_('plain text')
class TestThreadLocalState(PecanTestCase):
@@ -865,7 +873,7 @@ class TestThreadLocalState(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
- assert r.body == '/'
+ assert r.body == b_('/')
def test_request_state_cleanup(self):
"""
@@ -882,9 +890,9 @@ class TestThreadLocalState(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
- assert r.body == '/'
+ assert r.body == b_('/')
- assert state.__dict__.keys() == ['app']
+ assert list(state.__dict__.keys()) == ['app']
class TestFileTypeExtensions(PecanTestCase):
@@ -908,22 +916,22 @@ class TestFileTypeExtensions(PecanTestCase):
def test_html_extension(self):
r = self.app_.get('/index.html')
assert r.status_int == 200
- assert r.body == '.html'
+ assert r.body == b_('.html')
def test_image_extension(self):
r = self.app_.get('/image.png')
assert r.status_int == 200
- assert r.body == '.png'
+ assert r.body == b_('.png')
def test_hidden_file(self):
r = self.app_.get('/.vimrc')
assert r.status_int == 200
- assert r.body == ''
+ assert r.body == b_('')
def test_multi_dot_extension(self):
r = self.app_.get('/gradient.min.js')
assert r.status_int == 200
- assert r.body == '.js'
+ assert r.body == b_('.js')
def test_bad_content_type(self):
class RootController(object):
@@ -934,11 +942,11 @@ class TestFileTypeExtensions(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
- assert r.body == '/'
+ assert r.body == b_('/')
r = app.get('/index.html', expect_errors=True)
assert r.status_int == 200
- assert r.body == '/'
+ assert r.body == b_('/')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
@@ -957,7 +965,7 @@ class TestFileTypeExtensions(PecanTestCase):
r = app.get('/example:x.tiny')
assert r.status_int == 200
- assert r.body == 'SOME VALUE'
+ assert r.body == b_('SOME VALUE')
def test_guessing_disabled(self):
class RootController(object):
@@ -972,7 +980,7 @@ class TestFileTypeExtensions(PecanTestCase):
r = app.get('/index.html')
assert r.status_int == 200
- assert r.body == 'SOME VALUE'
+ assert r.body == b_('SOME VALUE')
class TestContentTypeByAcceptHeaders(PecanTestCase):
@@ -1057,12 +1065,12 @@ class TestCanonicalRouting(PecanTestCase):
def test_root(self):
r = self.app_.get('/')
assert r.status_int == 200
- assert 'index' in r.body
+ assert b_('index') in r.body
def test_index(self):
r = self.app_.get('/index')
assert r.status_int == 200
- assert 'index' in r.body
+ assert b_('index') in r.body
def test_broken_clients(self):
# for broken clients
@@ -1073,7 +1081,7 @@ class TestCanonicalRouting(PecanTestCase):
def test_sub_controller_with_trailing(self):
r = self.app_.get('/sub/')
assert r.status_int == 200
- assert 'subindex' in r.body
+ assert b_('subindex') in r.body
def test_sub_controller_redirect(self):
r = self.app_.get('/sub', status=302)
@@ -1090,23 +1098,23 @@ class TestCanonicalRouting(PecanTestCase):
try:
self.app_.post('/sub', dict(foo=1))
raise Exception("Post should fail") # pragma: nocover
- except Exception, e:
+ except Exception as e:
assert isinstance(e, RuntimeError)
def test_with_args(self):
r = self.app_.get('/arg/index/foo')
assert r.status_int == 200
- assert r.body == 'foo'
+ assert r.body == b_('foo')
def test_accept_noncanonical(self):
r = self.app_.get('/accept/')
assert r.status_int == 200
- assert 'accept' == r.body
+ assert r.body == b_('accept')
def test_accept_noncanonical_no_trailing_slash(self):
r = self.app_.get('/accept')
assert r.status_int == 200
- assert 'accept' == r.body
+ assert r.body == b_('accept')
class TestNonCanonical(PecanTestCase):
@@ -1143,22 +1151,22 @@ class TestNonCanonical(PecanTestCase):
def test_index(self):
r = self.app_.get('/')
assert r.status_int == 200
- assert 'index' in r.body
+ assert b_('index') in r.body
def test_subcontroller(self):
r = self.app_.get('/sub')
assert r.status_int == 200
- assert 'subindex' in r.body
+ assert b_('subindex') in r.body
def test_subcontroller_with_kwargs(self):
r = self.app_.post('/sub', dict(foo=1))
assert r.status_int == 200
- assert 'subindex' in r.body
+ assert b_('subindex') in r.body
def test_sub_controller_with_trailing(self):
r = self.app_.get('/sub/')
assert r.status_int == 200
- assert 'subindex' in r.body
+ assert b_('subindex') in r.body
def test_proxy(self):
class RootController(object):
@@ -1198,7 +1206,6 @@ class TestLogging(PecanTestCase):
logging.getLogger('pecantesting').info('HELLO WORLD')
return "HELLO WORLD"
- from cStringIO import StringIO
f = StringIO()
app = TestApp(make_app(RootController(), logging={
@@ -1227,7 +1234,6 @@ class TestLogging(PecanTestCase):
logging.getLogger('pecantesting').info('HELLO WORLD')
return "HELLO WORLD"
- from cStringIO import StringIO
f = StringIO()
from pecan.configuration import conf_from_dict
@@ -1271,16 +1277,16 @@ class TestEngines(PecanTestCase):
)
r = app.get('/')
assert r.status_int == 200
- assert "<h1>Hello, Jonathan!</h1>" in r.body
+ assert b_("<h1>Hello, Jonathan!</h1>") in r.body
r = app.get('/index.html?name=World')
assert r.status_int == 200
- assert "<h1>Hello, World!</h1>" in r.body
+ assert b_("<h1>Hello, World!</h1>") in r.body
error_msg = None
try:
r = app.get('/badtemplate.html')
- except Exception, e:
+ except Exception as e:
for error_f in error_formatters:
error_msg = error_f(e)
if error_msg:
@@ -1300,11 +1306,11 @@ class TestEngines(PecanTestCase):
)
r = app.get('/')
assert r.status_int == 200
- assert "<h1>Hello, Jonathan!</h1>" in r.body
+ assert b_("<h1>Hello, Jonathan!</h1>") in r.body
r = app.get('/index.html?name=World')
assert r.status_int == 200
- assert "<h1>Hello, World!</h1>" in r.body
+ assert b_("<h1>Hello, World!</h1>") in r.body
@unittest.skipIf('jinja' not in builtin_renderers, 'Jinja not installed')
def test_jinja(self):
@@ -1323,12 +1329,12 @@ class TestEngines(PecanTestCase):
)
r = app.get('/')
assert r.status_int == 200
- assert "<h1>Hello, Jonathan!</h1>" in r.body
+ assert b_("<h1>Hello, Jonathan!</h1>") in r.body
error_msg = None
try:
r = app.get('/badtemplate.html')
- except Exception, e:
+ except Exception as e:
for error_f in error_formatters:
error_msg = error_f(e)
if error_msg:
@@ -1352,16 +1358,16 @@ class TestEngines(PecanTestCase):
)
r = app.get('/')
assert r.status_int == 200
- assert "<h1>Hello, Jonathan!</h1>" in r.body
+ assert b_("<h1>Hello, Jonathan!</h1>") in r.body
r = app.get('/index.html?name=World')
assert r.status_int == 200
- assert "<h1>Hello, World!</h1>" in r.body
+ assert b_("<h1>Hello, World!</h1>") in r.body
error_msg = None
try:
r = app.get('/badtemplate.html')
- except Exception, e:
+ except Exception as e:
for error_f in error_formatters:
error_msg = error_f(e)
if error_msg:
@@ -1387,7 +1393,7 @@ class TestEngines(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
- result = dict(loads(r.body))
+ result = dict(loads(r.body.decode()))
assert result == expected_result
def test_override_template(self):
@@ -1400,7 +1406,7 @@ class TestEngines(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
- assert 'Override' in r.body
+ assert b_('Override') in r.body
assert r.content_type == 'text/plain'
def test_render(self):
@@ -1414,4 +1420,4 @@ class TestEngines(PecanTestCase):
)
r = app.get('/')
assert r.status_int == 200
- assert "<h1>Hello, Jonathan!</h1>" in r.body
+ assert b_("<h1>Hello, Jonathan!</h1>") in r.body
diff --git a/pecan/tests/test_generic.py b/pecan/tests/test_generic.py
index 82a6ca4..879ad26 100644
--- a/pecan/tests/test_generic.py
+++ b/pecan/tests/test_generic.py
@@ -4,6 +4,8 @@ try:
except:
from json import dumps # noqa
+from six import b as b_
+
from pecan import Pecan, expose
from pecan.tests import PecanTestCase
@@ -27,11 +29,11 @@ class TestGeneric(PecanTestCase):
app = TestApp(Pecan(RootController()))
r = app.get('/')
assert r.status_int == 200
- assert r.body == 'GET'
+ assert r.body == b_('GET')
r = app.post('/')
assert r.status_int == 200
- assert r.body == dumps(dict(result='POST'))
+ assert r.body == b_(dumps(dict(result='POST')))
r = app.get('/do_get', status=404)
assert r.status_int == 404
diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py
index 8e103b2..66947ca 100644
--- a/pecan/tests/test_hooks.py
+++ b/pecan/tests/test_hooks.py
@@ -1,9 +1,8 @@
-from cStringIO import StringIO
-
from webtest import TestApp
+from six import b as b_
+from six.moves import cStringIO as StringIO
from pecan import make_app, expose, redirect, abort
-from pecan.core import state
from pecan.hooks import (
PecanHook, TransactionHook, HookController, RequestViewerHook
)
@@ -39,7 +38,7 @@ class TestHooks(PecanTestCase):
app = TestApp(make_app(RootController(), hooks=[SimpleHook()]))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 4
assert run_hook[0] == 'on_route'
@@ -77,7 +76,7 @@ class TestHooks(PecanTestCase):
]))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 10
assert run_hook[0] == 'on_route1'
@@ -118,7 +117,7 @@ class TestHooks(PecanTestCase):
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello World!'
+ assert response.body == b_('Hello World!')
assert len(run_hook) == 2
assert run_hook[0] == 'on_route'
@@ -127,7 +126,7 @@ class TestHooks(PecanTestCase):
run_hook = []
try:
response = app.get('/causeerror')
- except Exception, e:
+ except Exception as e:
assert isinstance(e, IndexError)
assert len(run_hook) == 2
@@ -167,7 +166,7 @@ class TestHooks(PecanTestCase):
app = TestApp(papp)
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 10
assert run_hook[0] == 'on_route3'
@@ -224,7 +223,7 @@ class TestHooks(PecanTestCase):
app = TestApp(make_app(RootController()))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 1
assert run_hook[0] == 'inside'
@@ -233,7 +232,7 @@ class TestHooks(PecanTestCase):
response = app.get('/sub/')
assert response.status_int == 200
- assert response.body == 'Inside here!'
+ assert response.body == b_('Inside here!')
assert len(run_hook) == 3
assert run_hook[0] == 'before'
@@ -243,7 +242,7 @@ class TestHooks(PecanTestCase):
run_hook = []
response = app.get('/sub/sub/')
assert response.status_int == 200
- assert response.body == 'Deep inside here!'
+ assert response.body == b_('Deep inside here!')
assert len(run_hook) == 3
assert run_hook[0] == 'before'
@@ -288,7 +287,7 @@ class TestHooks(PecanTestCase):
app = TestApp(make_app(RootController(), hooks=[SimpleHook(1)]))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 4
assert run_hook[0] == 'on_route1'
@@ -300,7 +299,7 @@ class TestHooks(PecanTestCase):
response = app.get('/sub/')
assert response.status_int == 200
- assert response.body == 'Inside here!'
+ assert response.body == b_('Inside here!')
assert len(run_hook) == 6
assert run_hook[0] == 'on_route1'
@@ -344,7 +343,7 @@ class TestTransactionHook(PecanTestCase):
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 3
assert run_hook[0] == 'start_ro'
@@ -355,7 +354,7 @@ class TestTransactionHook(PecanTestCase):
response = app.post('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 4
assert run_hook[0] == 'start'
@@ -450,7 +449,7 @@ class TestTransactionHook(PecanTestCase):
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Index Method!'
+ assert response.body == b_('Index Method!')
assert len(run_hook) == 3
assert run_hook[0] == 'start_ro'
@@ -461,7 +460,7 @@ class TestTransactionHook(PecanTestCase):
response = app.post('/')
assert response.status_int == 200
- assert response.body == 'Index Method!'
+ assert response.body == b_('Index Method!')
assert len(run_hook) == 5
assert run_hook[0] == 'start'
@@ -474,7 +473,7 @@ class TestTransactionHook(PecanTestCase):
response = app.get('/decorated')
assert response.status_int == 200
- assert response.body == 'Decorated Method!'
+ assert response.body == b_('Decorated Method!')
assert len(run_hook) == 7
assert run_hook[0] == 'start_ro'
@@ -579,7 +578,7 @@ class TestTransactionHook(PecanTestCase):
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 3
assert run_hook[0] == 'start_ro'
@@ -592,7 +591,7 @@ class TestTransactionHook(PecanTestCase):
response = app.post('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 4
assert run_hook[0] == 'start'
@@ -822,7 +821,7 @@ class TestTransactionHook(PecanTestCase):
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 6
assert run_hook[0] == 'start_ro'
@@ -838,7 +837,7 @@ class TestTransactionHook(PecanTestCase):
response = app.post('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert len(run_hook) == 4
assert run_hook[0] == 'start'
@@ -947,7 +946,7 @@ class TestTransactionHook(PecanTestCase):
response = app.get('/generic')
assert response.status_int == 200
- assert response.body == 'generic get'
+ assert response.body == b_('generic get')
assert len(run_hook) == 6
assert run_hook[0] == 'start_ro'
assert run_hook[1] == 'clear'
@@ -965,7 +964,7 @@ class TestTransactionHook(PecanTestCase):
response = app.post('/generic')
assert response.status_int == 200
- assert response.body == 'generic post'
+ assert response.body == b_('generic post')
assert len(run_hook) == 4
assert run_hook[0] == 'start'
assert run_hook[1] == 'inside'
@@ -1052,7 +1051,7 @@ class TestRequestViewerHook(PecanTestCase):
out = _stdout.getvalue()
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert 'path' in out
assert 'method' in out
assert 'status' in out
@@ -1117,7 +1116,7 @@ class TestRequestViewerHook(PecanTestCase):
out = _stdout.getvalue()
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert '/' in out
assert 'path' in out
assert 'method' not in out
@@ -1152,7 +1151,7 @@ class TestRequestViewerHook(PecanTestCase):
out = _stdout.getvalue()
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert out == ''
def test_item_not_in_defaults(self):
@@ -1179,7 +1178,7 @@ class TestRequestViewerHook(PecanTestCase):
out = _stdout.getvalue()
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
assert 'date' in out
assert 'method' not in out
assert 'status' not in out
diff --git a/pecan/tests/test_jsonify.py b/pecan/tests/test_jsonify.py
index 1c1c589..2dcd663 100644
--- a/pecan/tests/test_jsonify.py
+++ b/pecan/tests/test_jsonify.py
@@ -72,7 +72,7 @@ class TestJsonify(PecanTestCase):
r = app.get('/')
assert r.status_int == 200
- assert loads(r.body) == {'name': 'Jonathan LaCour'}
+ assert loads(r.body.decode()) == {'name': 'Jonathan LaCour'}
class TestJsonifyGenericEncoder(PecanTestCase):
@@ -203,8 +203,8 @@ class TestJsonifySQLAlchemyGenericEncoder(PecanTestCase):
# add some dummy data
user_table.insert().execute([
- {'first_name': u'Jonathan', 'last_name': u'LaCour'},
- {'first_name': u'Yoann', 'last_name': u'Roman'}
+ {'first_name': 'Jonathan', 'last_name': 'LaCour'},
+ {'first_name': 'Yoann', 'last_name': 'Roman'}
])
# get the SA objects
diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py
index d5e7f5e..c1f8acc 100644
--- a/pecan/tests/test_rest.py
+++ b/pecan/tests/test_rest.py
@@ -5,6 +5,8 @@ try:
except:
from json import dumps, loads # noqa
+from six import b as b_
+
from pecan import abort, expose, make_app, response
from pecan.rest import RestController
from pecan.tests import PecanTestCase
@@ -103,38 +105,38 @@ class TestRestController(PecanTestCase):
# test get_all
r = app.get('/things')
assert r.status_int == 200
- assert r.body == dumps(dict(items=ThingsController.data))
+ assert r.body == b_(dumps(dict(items=ThingsController.data)))
# test get_one
for i, value in enumerate(ThingsController.data):
r = app.get('/things/%d' % i)
assert r.status_int == 200
- assert r.body == value
+ assert r.body == b_(value)
# test post
r = app.post('/things', {'value': 'four'})
assert r.status_int == 302
- assert r.body == 'CREATED'
+ assert r.body == b_('CREATED')
# make sure it works
r = app.get('/things/4')
assert r.status_int == 200
- assert r.body == 'four'
+ assert r.body == b_('four')
# test edit
r = app.get('/things/3/edit')
assert r.status_int == 200
- assert r.body == 'EDIT three'
+ assert r.body == b_('EDIT three')
# test put
r = app.put('/things/4', {'value': 'FOUR'})
assert r.status_int == 200
- assert r.body == 'UPDATED'
+ assert r.body == b_('UPDATED')
# make sure it works
r = app.get('/things/4')
assert r.status_int == 200
- assert r.body == 'FOUR'
+ assert r.body == b_('FOUR')
# test put with _method parameter and GET
r = app.get('/things/4?_method=put', {'value': 'FOUR!'}, status=405)
@@ -143,32 +145,32 @@ class TestRestController(PecanTestCase):
# make sure it works
r = app.get('/things/4')
assert r.status_int == 200
- assert r.body == 'FOUR'
+ assert r.body == b_('FOUR')
# test put with _method parameter and POST
r = app.post('/things/4?_method=put', {'value': 'FOUR!'})
assert r.status_int == 200
- assert r.body == 'UPDATED'
+ assert r.body == b_('UPDATED')
# make sure it works
r = app.get('/things/4')
assert r.status_int == 200
- assert r.body == 'FOUR!'
+ assert r.body == b_('FOUR!')
# test get delete
r = app.get('/things/4/delete')
assert r.status_int == 200
- assert r.body == 'DELETE FOUR!'
+ assert r.body == b_('DELETE FOUR!')
# test delete
r = app.delete('/things/4')
assert r.status_int == 200
- assert r.body == 'DELETED'
+ assert r.body == b_('DELETED')
# make sure it works
r = app.get('/things')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 4
+ assert len(loads(r.body.decode())['items']) == 4
# test delete with _method parameter and GET
r = app.get('/things/3?_method=DELETE', status=405)
@@ -177,37 +179,37 @@ class TestRestController(PecanTestCase):
# make sure it works
r = app.get('/things')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 4
+ assert len(loads(r.body.decode())['items']) == 4
# test delete with _method parameter and POST
r = app.post('/things/3?_method=DELETE')
assert r.status_int == 200
- assert r.body == 'DELETED'
+ assert r.body == b_('DELETED')
# make sure it works
r = app.get('/things')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 3
+ assert len(loads(r.body.decode())['items']) == 3
# test "RESET" custom action
r = app.request('/things', method='RESET')
assert r.status_int == 200
- assert r.body == 'RESET'
+ assert r.body == b_('RESET')
# test "RESET" custom action with _method parameter
r = app.get('/things?_method=RESET')
assert r.status_int == 200
- assert r.body == 'RESET'
+ assert r.body == b_('RESET')
# test the "OPTIONS" custom action
r = app.request('/things', method='OPTIONS')
assert r.status_int == 200
- assert r.body == 'OPTIONS'
+ assert r.body == b_('OPTIONS')
# test the "OPTIONS" custom action with the _method parameter
r = app.post('/things', {'_method': 'OPTIONS'})
assert r.status_int == 200
- assert r.body == 'OPTIONS'
+ assert r.body == b_('OPTIONS')
# test the "other" custom action
with warnings.catch_warnings():
@@ -224,7 +226,7 @@ class TestRestController(PecanTestCase):
warnings.simplefilter("ignore")
r = app.request('/things/others/', method='MISC')
assert r.status_int == 200
- assert r.body == 'OTHERS'
+ assert r.body == b_('OTHERS')
# test the "others" custom action missing trailing slash
with warnings.catch_warnings():
@@ -235,7 +237,7 @@ class TestRestController(PecanTestCase):
# test the "others" custom action with the _method parameter
r = app.get('/things/others/?_method=MISC')
assert r.status_int == 200
- assert r.body == 'OTHERS'
+ assert r.body == b_('OTHERS')
# test an invalid custom action
r = app.get('/things?_method=BAD', status=404)
@@ -244,27 +246,27 @@ class TestRestController(PecanTestCase):
# test custom "GET" request "count"
r = app.get('/things/count')
assert r.status_int == 200
- assert r.body == '3'
+ assert r.body == b_('3')
# test custom "GET" request "length"
r = app.get('/things/1/length')
assert r.status_int == 200
- assert r.body == str(len('one'))
+ assert r.body == b_(str(len('one')))
# test custom "GET" request through subcontroller
r = app.get('/things/others/echo?value=test')
assert r.status_int == 200
- assert r.body == 'test'
+ assert r.body == b_('test')
# test custom "POST" request "length"
r = app.post('/things/1/length', {'value': 'test'})
assert r.status_int == 200
- assert r.body == str(len('onetest'))
+ assert r.body == b_(str(len('onetest')))
# test custom "POST" request through subcontroller
r = app.post('/things/others/echo', {'value': 'test'})
assert r.status_int == 200
- assert r.body == 'test'
+ assert r.body == b_('test')
def test_getall_with_trailing_slash(self):
@@ -285,7 +287,7 @@ class TestRestController(PecanTestCase):
# test get_all
r = app.get('/things/')
assert r.status_int == 200
- assert r.body == dumps(dict(items=ThingsController.data))
+ assert r.body == b_(dumps(dict(items=ThingsController.data)))
def test_simple_nested_rest(self):
@@ -299,14 +301,6 @@ class TestRestController(PecanTestCase):
def delete(self, id_):
return "BAR-%s" % id_
- @expose()
- def post(self):
- return "BAR-POST"
-
- @expose()
- def delete(self, id_):
- return "BAR-%s" % id_
-
class FooController(RestController):
bar = BarController()
@@ -327,19 +321,19 @@ class TestRestController(PecanTestCase):
r = app.post('/foo')
assert r.status_int == 200
- assert r.body == "FOO-POST"
+ assert r.body == b_("FOO-POST")
r = app.delete('/foo/1')
assert r.status_int == 200
- assert r.body == "FOO-1"
+ assert r.body == b_("FOO-1")
r = app.post('/foo/bar')
assert r.status_int == 200
- assert r.body == "BAR-POST"
+ assert r.body == b_("BAR-POST")
r = app.delete('/foo/bar/2')
assert r.status_int == 200
- assert r.body == "BAR-2"
+ assert r.body == b_("BAR-2")
def test_complicated_nested_rest(self):
@@ -437,75 +431,75 @@ class TestRestController(PecanTestCase):
# test get_all
r = app.get('/foos')
assert r.status_int == 200
- assert r.body == dumps(dict(items=FoosController.data))
+ assert r.body == b_(dumps(dict(items=FoosController.data)))
# test nested get_all
r = app.get('/foos/1/bars')
assert r.status_int == 200
- assert r.body == dumps(dict(items=BarsController.data[1]))
+ assert r.body == b_(dumps(dict(items=BarsController.data[1])))
# test get_one
for i, value in enumerate(FoosController.data):
r = app.get('/foos/%d' % i)
assert r.status_int == 200
- assert r.body == value
+ assert r.body == b_(value)
# test nested get_one
for i, value in enumerate(FoosController.data):
for j, value in enumerate(BarsController.data[i]):
r = app.get('/foos/%s/bars/%s' % (i, j))
assert r.status_int == 200
- assert r.body == value
+ assert r.body == b_(value)
# test post
r = app.post('/foos', {'value': 'two'})
assert r.status_int == 302
- assert r.body == 'CREATED'
+ assert r.body == b_('CREATED')
# make sure it works
r = app.get('/foos/2')
assert r.status_int == 200
- assert r.body == 'two'
+ assert r.body == b_('two')
# test nested post
r = app.post('/foos/2/bars', {'value': 'two-zero'})
assert r.status_int == 302
- assert r.body == 'CREATED FOR 2'
+ assert r.body == b_('CREATED FOR 2')
# make sure it works
r = app.get('/foos/2/bars/0')
assert r.status_int == 200
- assert r.body == 'two-zero'
+ assert r.body == b_('two-zero')
# test edit
r = app.get('/foos/1/edit')
assert r.status_int == 200
- assert r.body == 'EDIT one'
+ assert r.body == b_('EDIT one')
# test nested edit
r = app.get('/foos/1/bars/1/edit')
assert r.status_int == 200
- assert r.body == 'EDIT one-one'
+ assert r.body == b_('EDIT one-one')
# test put
r = app.put('/foos/2', {'value': 'TWO'})
assert r.status_int == 200
- assert r.body == 'UPDATED'
+ assert r.body == b_('UPDATED')
# make sure it works
r = app.get('/foos/2')
assert r.status_int == 200
- assert r.body == 'TWO'
+ assert r.body == b_('TWO')
# test nested put
r = app.put('/foos/2/bars/0', {'value': 'TWO-ZERO'})
assert r.status_int == 200
- assert r.body == 'UPDATED'
+ assert r.body == b_('UPDATED')
# make sure it works
r = app.get('/foos/2/bars/0')
assert r.status_int == 200
- assert r.body == 'TWO-ZERO'
+ assert r.body == b_('TWO-ZERO')
# test put with _method parameter and GET
r = app.get('/foos/2?_method=put', {'value': 'TWO!'}, status=405)
@@ -514,7 +508,7 @@ class TestRestController(PecanTestCase):
# make sure it works
r = app.get('/foos/2')
assert r.status_int == 200
- assert r.body == 'TWO'
+ assert r.body == b_('TWO')
# test nested put with _method parameter and GET
r = app.get(
@@ -526,57 +520,57 @@ class TestRestController(PecanTestCase):
# make sure it works
r = app.get('/foos/2/bars/0')
assert r.status_int == 200
- assert r.body == 'TWO-ZERO'
+ assert r.body == b_('TWO-ZERO')
# test put with _method parameter and POST
r = app.post('/foos/2?_method=put', {'value': 'TWO!'})
assert r.status_int == 200
- assert r.body == 'UPDATED'
+ assert r.body == b_('UPDATED')
# make sure it works
r = app.get('/foos/2')
assert r.status_int == 200
- assert r.body == 'TWO!'
+ assert r.body == b_('TWO!')
# test nested put with _method parameter and POST
r = app.post('/foos/2/bars/0?_method=put', {'value': 'TWO-ZERO!'})
assert r.status_int == 200
- assert r.body == 'UPDATED'
+ assert r.body == b_('UPDATED')
# make sure it works
r = app.get('/foos/2/bars/0')
assert r.status_int == 200
- assert r.body == 'TWO-ZERO!'
+ assert r.body == b_('TWO-ZERO!')
# test get delete
r = app.get('/foos/2/delete')
assert r.status_int == 200
- assert r.body == 'DELETE TWO!'
+ assert r.body == b_('DELETE TWO!')
# test nested get delete
r = app.get('/foos/2/bars/0/delete')
assert r.status_int == 200
- assert r.body == 'DELETE TWO-ZERO!'
+ assert r.body == b_('DELETE TWO-ZERO!')
# test nested delete
r = app.delete('/foos/2/bars/0')
assert r.status_int == 200
- assert r.body == 'DELETED'
+ assert r.body == b_('DELETED')
# make sure it works
r = app.get('/foos/2/bars')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 0
+ assert len(loads(r.body.decode())['items']) == 0
# test delete
r = app.delete('/foos/2')
assert r.status_int == 200
- assert r.body == 'DELETED'
+ assert r.body == b_('DELETED')
# make sure it works
r = app.get('/foos')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 2
+ assert len(loads(r.body.decode())['items']) == 2
# test nested delete with _method parameter and GET
r = app.get('/foos/1/bars/1?_method=DELETE', status=405)
@@ -585,7 +579,7 @@ class TestRestController(PecanTestCase):
# make sure it works
r = app.get('/foos/1/bars')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 2
+ assert len(loads(r.body.decode())['items']) == 2
# test delete with _method parameter and GET
r = app.get('/foos/1?_method=DELETE', status=405)
@@ -594,27 +588,27 @@ class TestRestController(PecanTestCase):
# make sure it works
r = app.get('/foos')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 2
+ assert len(loads(r.body.decode())['items']) == 2
# test nested delete with _method parameter and POST
r = app.post('/foos/1/bars/1?_method=DELETE')
assert r.status_int == 200
- assert r.body == 'DELETED'
+ assert r.body == b_('DELETED')
# make sure it works
r = app.get('/foos/1/bars')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 1
+ assert len(loads(r.body.decode())['items']) == 1
# test delete with _method parameter and POST
r = app.post('/foos/1?_method=DELETE')
assert r.status_int == 200
- assert r.body == 'DELETED'
+ assert r.body == b_('DELETED')
# make sure it works
r = app.get('/foos')
assert r.status_int == 200
- assert len(loads(r.body)['items']) == 1
+ assert len(loads(r.body.decode())['items']) == 1
def test_bad_rest(self):
@@ -717,7 +711,7 @@ class TestRestController(PecanTestCase):
# test custom delete without ID
r = app.delete('/things/others/')
assert r.status_int == 200
- assert r.body == 'DELETE'
+ assert r.body == b_('DELETE')
# test custom delete without ID with _method parameter and GET
r = app.get('/things/others/?_method=delete', status=405)
@@ -726,12 +720,12 @@ class TestRestController(PecanTestCase):
# test custom delete without ID with _method parameter and POST
r = app.post('/things/others/', {'_method': 'delete'})
assert r.status_int == 200
- assert r.body == 'DELETE'
+ assert r.body == b_('DELETE')
# test custom delete with ID
r = app.delete('/things/others/reset/1')
assert r.status_int == 200
- assert r.body == '1'
+ assert r.body == b_('1')
# test custom delete with ID with _method parameter and GET
r = app.get('/things/others/reset/1?_method=delete', status=405)
@@ -740,7 +734,7 @@ class TestRestController(PecanTestCase):
# test custom delete with ID with _method parameter and POST
r = app.post('/things/others/reset/1', {'_method': 'delete'})
assert r.status_int == 200
- assert r.body == '1'
+ assert r.body == b_('1')
def test_get_with_var_args(self):
@@ -767,12 +761,12 @@ class TestRestController(PecanTestCase):
# test get request
r = app.get('/things/one/two/three')
assert r.status_int == 200
- assert r.body == 'one, two, three'
+ assert r.body == b_('one, two, three')
# test nested get request
r = app.get('/things/one/two/three/others/')
assert r.status_int == 200
- assert r.body == 'NESTED: one, two, three'
+ assert r.body == b_('NESTED: one, two, three')
def test_sub_nested_rest(self):
@@ -813,7 +807,7 @@ class TestRestController(PecanTestCase):
# test sub-nested get_one
r = app.get('/foos/0/bars/0/bazs/0')
assert r.status_int == 200
- assert r.body == 'zero-zero-zero'
+ assert r.body == b_('zero-zero-zero')
def test_sub_nested_rest_with_overwrites(self):
@@ -889,35 +883,35 @@ class TestRestController(PecanTestCase):
r = app.post('/foos')
assert r.status_int == 200
- assert r.body == 'POST'
+ assert r.body == b_('POST')
r = app.put('/foos/0')
assert r.status_int == 200
- assert r.body == 'PUT'
+ assert r.body == b_('PUT')
r = app.post('/foos/bars')
assert r.status_int == 200
- assert r.body == 'POST-CHILD'
+ assert r.body == b_('POST-CHILD')
r = app.put('/foos/bars/0')
assert r.status_int == 200
- assert r.body == 'PUT-CHILD'
+ assert r.body == b_('PUT-CHILD')
r = app.post('/foos/bars/bazs')
assert r.status_int == 200
- assert r.body == 'POST-GRAND-CHILD'
+ assert r.body == b_('POST-GRAND-CHILD')
r = app.put('/foos/bars/bazs/0')
assert r.status_int == 200
- assert r.body == 'PUT-GRAND-CHILD'
+ assert r.body == b_('PUT-GRAND-CHILD')
r = app.get('/foos/bars/bazs/final/')
assert r.status_int == 200
- assert r.body == 'FINAL'
+ assert r.body == b_('FINAL')
r = app.get('/foos/bars/bazs/final/named')
assert r.status_int == 200
- assert r.body == 'NAMED'
+ assert r.body == b_('NAMED')
def test_post_with_kwargs_only(self):
@@ -936,7 +930,7 @@ class TestRestController(PecanTestCase):
r = app.get('/')
assert r.status_int == 200
- assert r.body == 'INDEX'
+ assert r.body == b_('INDEX')
kwargs = {'foo': 'bar', 'spam': 'eggs'}
r = app.post('/', kwargs)
@@ -1025,43 +1019,43 @@ class TestRestController(PecanTestCase):
r = app.get('/foo')
assert r.status_int == 200
- assert r.body == 'INDEX'
+ assert r.body == b_('INDEX')
r = app.post('/foo')
assert r.status_int == 200
- assert r.body == 'POST'
+ assert r.body == b_('POST')
r = app.get('/foo/1')
assert r.status_int == 200
- assert r.body == 'GET ONE'
+ assert r.body == b_('GET ONE')
r = app.post('/foo/1')
assert r.status_int == 200
- assert r.body == 'POST-LOOKUP-1'
+ assert r.body == b_('POST-LOOKUP-1')
r = app.put('/foo/1')
assert r.status_int == 200
- assert r.body == 'PUT-1'
+ assert r.body == b_('PUT-1')
r = app.delete('/foo/1')
assert r.status_int == 200
- assert r.body == 'DELETE-1'
+ assert r.body == b_('DELETE-1')
r = app.put('/foo/1/2')
assert r.status_int == 200
- assert r.body == 'PUT-LOOKUP-1-2'
+ assert r.body == b_('PUT-LOOKUP-1-2')
r = app.delete('/foo/1/2')
assert r.status_int == 200
- assert r.body == 'DELETE-LOOKUP-1-2'
+ assert r.body == b_('DELETE-LOOKUP-1-2')
r = app.get('/foo/1/2')
assert r.status_int == 200
- assert r.body == 'FINAL-2'
+ assert r.body == b_('FINAL-2')
r = app.post('/foo/1/2')
assert r.status_int == 200
- assert r.body == 'POST-2'
+ assert r.body == b_('POST-2')
def test_dynamic_rest_lookup(self):
class BarController(RestController):
@@ -1125,40 +1119,40 @@ class TestRestController(PecanTestCase):
r = app.get('/foos')
assert r.status_int == 200
- assert r.body == 'FOOS'
+ assert r.body == b_('FOOS')
r = app.post('/foos')
assert r.status_int == 200
- assert r.body == 'POST_FOOS'
+ assert r.body == b_('POST_FOOS')
r = app.get('/foos/foo')
assert r.status_int == 200
- assert r.body == 'FOO'
+ assert r.body == b_('FOO')
r = app.put('/foos/foo')
assert r.status_int == 200
- assert r.body == 'PUT_FOO'
+ assert r.body == b_('PUT_FOO')
r = app.delete('/foos/foo')
assert r.status_int == 200
- assert r.body == 'DELETE_FOO'
+ assert r.body == b_('DELETE_FOO')
r = app.get('/foos/foo/bars')
assert r.status_int == 200
- assert r.body == 'BARS'
+ assert r.body == b_('BARS')
r = app.post('/foos/foo/bars')
assert r.status_int == 200
- assert r.body == 'POST_BARS'
+ assert r.body == b_('POST_BARS')
r = app.get('/foos/foo/bars/bar')
assert r.status_int == 200
- assert r.body == 'BAR'
+ assert r.body == b_('BAR')
r = app.put('/foos/foo/bars/bar')
assert r.status_int == 200
- assert r.body == 'PUT_BAR'
+ assert r.body == b_('PUT_BAR')
r = app.delete('/foos/foo/bars/bar')
assert r.status_int == 200
- assert r.body == 'DELETE_BAR'
+ assert r.body == b_('DELETE_BAR')
diff --git a/pecan/tests/test_scaffolds.py b/pecan/tests/test_scaffolds.py
index b0168e3..669aa7d 100644
--- a/pecan/tests/test_scaffolds.py
+++ b/pecan/tests/test_scaffolds.py
@@ -2,7 +2,8 @@ import os
import sys
import tempfile
import shutil
-from cStringIO import StringIO
+
+from six.moves import cStringIO as StringIO
from pecan.tests import PecanTestCase
@@ -94,7 +95,6 @@ class TestScaffoldUtils(PecanTestCase):
def test_destination_directory_already_exists(self):
from pecan.scaffolds import copy_dir
- from cStringIO import StringIO
f = StringIO()
copy_dir(
(
diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py
index 1fb1fd9..53a63ca 100644
--- a/pecan/tests/test_secure.py
+++ b/pecan/tests/test_secure.py
@@ -5,6 +5,7 @@ if sys.version_info < (2, 7):
else:
import unittest # noqa
+from six import b as b_
from webtest import TestApp
from pecan import expose, make_app
@@ -59,11 +60,11 @@ class TestSecure(PecanTestCase):
))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
response = app.get('/unlocked')
assert response.status_int == 200
- assert response.body == 'Sure thing'
+ assert response.body == b_('Sure thing')
response = app.get('/locked', expect_errors=True)
assert response.status_int == 401
@@ -73,7 +74,7 @@ class TestSecure(PecanTestCase):
response = app.get('/secret/allowed')
assert response.status_int == 200
- assert response.body == 'Allowed!'
+ assert response.body == b_('Allowed!')
def test_unlocked_attribute(self):
class AuthorizedSubController(object):
@@ -121,11 +122,11 @@ class TestSecure(PecanTestCase):
))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello, World!'
+ assert response.body == b_('Hello, World!')
response = app.get('/unlocked')
assert response.status_int == 200
- assert response.body == 'Sure thing'
+ assert response.body == b_('Sure thing')
response = app.get('/locked', expect_errors=True)
assert response.status_int == 401
@@ -135,15 +136,15 @@ class TestSecure(PecanTestCase):
response = app.get('/secret/allowed')
assert response.status_int == 200
- assert response.body == 'Allowed!'
+ assert response.body == b_('Allowed!')
response = app.get('/secret/authorized/')
assert response.status_int == 200
- assert response.body == 'Index'
+ assert response.body == b_('Index')
response = app.get('/secret/authorized/allowed')
assert response.status_int == 200
- assert response.body == 'Allowed!'
+ assert response.body == b_('Allowed!')
def test_secure_attribute(self):
authorized = False
@@ -163,7 +164,7 @@ class TestSecure(PecanTestCase):
app = TestApp(make_app(RootController()))
response = app.get('/')
assert response.status_int == 200
- assert response.body == 'Hello from root!'
+ assert response.body == b_('Hello from root!')
response = app.get('/sub/', expect_errors=True)
assert response.status_int == 401
@@ -171,7 +172,7 @@ class TestSecure(PecanTestCase):
authorized = True
response = app.get('/sub/')
assert response.status_int == 200
- assert response.body == 'Hello from sub!'
+ assert response.body == b_('Hello from sub!')
def test_state_attribute(self):
from pecan.secure import Any, Protected
@@ -187,7 +188,7 @@ class TestSecure(PecanTestCase):
try:
secure(Foo())
- except Exception, e:
+ except Exception as e:
assert isinstance(e, TypeError)
@@ -288,7 +289,7 @@ class TestObjectPathSecurity(PecanTestCase):
def test_sub_of_both_not_secret(self):
response = self.app.get('/notsecret/hi/')
assert response.status_int == 200
- assert response.body == 'Index hi'
+ assert response.body == b_('Index hi')
def test_protected_lookup(self):
response = self.app.get('/secret/hi/', expect_errors=True)
@@ -297,7 +298,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.authorized = True
response = self.app.get('/secret/hi/')
assert response.status_int == 200
- assert response.body == 'Index hi'
+ assert response.body == b_('Index hi')
assert 'secretcontroller' in self.permissions_checked
def test_secured_notfound_lookup(self):
@@ -324,7 +325,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.deepsecret_cls.authorized = True
response = self.app.get('/secret/hi/deepsecret/')
assert response.status_int == 200
- assert response.body == 'Deep Secret'
+ assert response.body == b_('Deep Secret')
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' in self.permissions_checked
@@ -333,14 +334,14 @@ class TestObjectPathSecurity(PecanTestCase):
self.deepsecret_cls.authorized = True
response = self.app.get('/secret/1/deepsecret/2/deepsecret/')
assert response.status_int == 200
- assert response.body == 'Deep Secret'
+ assert response.body == b_('Deep Secret')
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' in self.permissions_checked
def test_unlocked_lookup(self):
response = self.app.get('/notsecret/1/deepsecret/2/')
assert response.status_int == 200
- assert response.body == 'Index 2'
+ assert response.body == b_('Index 2')
assert 'deepsecret' not in self.permissions_checked
response = self.app.get(
@@ -368,7 +369,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/independent')
assert response.status_int == 200
- assert response.body == 'Independent Security'
+ assert response.body == b_('Independent Security')
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
@@ -383,7 +384,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/wrapped/')
assert response.status_int == 200
- assert response.body == 'Index wrapped'
+ assert response.body == b_('Index wrapped')
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
@@ -392,7 +393,7 @@ class TestObjectPathSecurity(PecanTestCase):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/lookup_wrapped/')
assert response.status_int == 200
- assert response.body == 'Index wrapped'
+ assert response.body == b_('Index wrapped')
assert len(self.permissions_checked) == 2
assert 'independent' in self.permissions_checked
assert 'secretcontroller' in self.permissions_checked
@@ -400,7 +401,7 @@ class TestObjectPathSecurity(PecanTestCase):
def test_unlocked_attribute_in_insecure(self):
response = self.app.get('/notsecret/unlocked/')
assert response.status_int == 200
- assert response.body == 'Index unlocked'
+ assert response.body == b_('Index unlocked')
class SecureControllerSharedPermissionsRegression(PecanTestCase):
diff --git a/pecan/tests/test_templating.py b/pecan/tests/test_templating.py
index 90ef4fb..5fffb0c 100644
--- a/pecan/tests/test_templating.py
+++ b/pecan/tests/test_templating.py
@@ -1,8 +1,10 @@
+import tempfile
+
+from six import b as b_
+
from pecan.templating import RendererFactory, format_line_context
from pecan.tests import PecanTestCase
-import tempfile
-
class TestTemplate(PecanTestCase):
def setUp(self):
@@ -42,7 +44,7 @@ class TestTemplateLineFormat(PecanTestCase):
def test_format_line_context(self):
for i in range(11):
- self.f.write('Testing Line %d\n' % i)
+ self.f.write(b_('Testing Line %d\n' % i))
self.f.flush()
assert format_line_context(self.f.name, 0).count('Testing Line') == 10
diff --git a/requirements.txt b/requirements.txt
index 7925821..1921b6f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,5 +2,4 @@ Mako==0.6.2
MarkupSafe==0.15
WebOb==1.2b3
WebTest==1.3.3
-simplegeneric==0.8
-wsgiref==0.1.2
+simplegeneric==0.8.1
diff --git a/setup.py b/setup.py
index 9d1f5fc..3bb1ff5 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,8 @@
import sys
+
from setuptools import setup, find_packages
-from setuptools.command.test import test as TestCommand
-version = '0.2.4'
+version = '0.3.0b'
#
# determine requirements
@@ -11,7 +11,8 @@ requirements = [
"WebOb >= 1.2dev", # py3 compat
"simplegeneric >= 0.8", # py3 compat
"Mako >= 0.4.0",
- "WebTest >= 1.3.1" # py3 compat
+ "WebTest >= 1.3.1", # py3 compat
+ "six"
]
try:
@@ -23,14 +24,25 @@ except:
requirements.append("simplejson >= 2.1.1")
try:
+ from logging.config import dictConfig # noqa
+except ImportError:
+ #
+ # This was introduced in Python 2.7 - the logutils package contains
+ # a backported replacement for 2.6
+ #
+ requirements.append('logutils')
+
+try:
import argparse # noqa
except:
+ #
+ # This was introduced in Python 2.7 - the argparse package contains
+ # a backported replacement for 2.6
+ #
requirements.append('argparse')
tests_require = requirements + [
'virtualenv',
- 'Genshi',
- 'Kajiki',
'Jinja2',
'gunicorn',
'mock'
@@ -38,6 +50,14 @@ tests_require = requirements + [
if sys.version_info < (2, 7):
tests_require += ['unittest2']
+if sys.version_info < (3, 0):
+ # These don't support Python3 yet - don't run their tests
+ tests_require += ['Kajiki']
+ tests_require += ['Genshi']
+else:
+ # Genshi added Python3 support in 0.7
+ tests_require += ['Genshi>=0.7']
+
#
# call setup
#
@@ -58,8 +78,12 @@ setup(
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
'Topic :: Internet :: WWW/HTTP :: WSGI',
'Topic :: Software Development :: Libraries :: Application Frameworks'
],
diff --git a/tox.ini b/tox.ini
index 3c83bc2..951b808 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26,py27,scaffolds-26,scaffolds-27,pep8
+envlist = py26,py27,py32,py33,scaffolds-26,scaffolds-27,scaffolds-32,scaffolds-33,pep8
[testenv]
commands={envpython} setup.py test -v
@@ -26,6 +26,32 @@ commands=pecan create testing123
pep8 --repeat --show-source testing123/setup.py testing123/testing123
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+[testenv:scaffolds-32]
+basepython = python3.2
+deps = pep8
+ gunicorn
+changedir={[testenv:scaffolds-26]changedir}
+commands=pecan create testing123
+ curl "http://python-distribute.org/distribute_setup.py" -O
+ {envpython} distribute_setup.py
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
+[testenv:scaffolds-33]
+basepython = python3.3
+deps = pep8
+ gunicorn
+changedir={[testenv:scaffolds-26]changedir}
+commands=pecan create testing123
+ curl "http://python-distribute.org/distribute_setup.py" -O
+ {envpython} distribute_setup.py
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
[testenv:pep8]
deps = pep8
-commands = pep8 --repeat --show-source --exclude *compat*,resources.py pecan setup.py
+commands = pep8 --repeat --show-source pecan setup.py