diff options
author | Jonathan LaCour <jonathan@cleverdevil.org> | 2013-05-07 09:03:02 -0700 |
---|---|---|
committer | Jonathan LaCour <jonathan@cleverdevil.org> | 2013-05-07 09:03:02 -0700 |
commit | 162d65a76b206330d4454d153c47a18d17d8fc8d (patch) | |
tree | 7716132858bcd8668b5f850ee4434061ffc7b5dc | |
parent | e985be86ae89eccfe3c344196112b31a265ab741 (diff) | |
parent | 77f97bf9f7c77e2a82a178ac883eef77f7eab249 (diff) | |
download | pecan-162d65a76b206330d4454d153c47a18d17d8fc8d.tar.gz |
Merge pull request #212 from ryanpetrello/py3k
Add support for Python 3.2 and 3.3
43 files changed, 656 insertions, 1816 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/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 @@ -1,6 +1,6 @@ import sys + from setuptools import setup, find_packages -from setuptools.command.test import test as TestCommand version = '0.2.4' @@ -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' ], @@ -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 |