diff --git a/.gitignore b/.gitignore index 943812bc..e64473a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.egg-info/ aspen/tests/log env +.tox/ *.pyc distribute-* __pycache__ diff --git a/.travis.yml b/.travis.yml index 303cb7e9..3c8c1a87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,12 @@ branches: - master language: python python: - - 2.7 + - 3.5 before_install: - pip install --upgrade pip - pip --version install: python build.py dev -script: python build.py analyse +script: python build.py test_cov notifications: email: false irc: diff --git a/appveyor.yml b/appveyor.yml index ee9c9250..4af1092b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ build: false # Not a C# project, build stuff at the test step instead. test_script: # Build the compiled extension and run the project tests - - "python build.py analyse" + - "python build.py test_cov" branches: only: diff --git a/aspen/configuration/__init__.py b/aspen/configuration/__init__.py index 844201b1..5eb71a68 100644 --- a/aspen/configuration/__init__.py +++ b/aspen/configuration/__init__.py @@ -6,7 +6,6 @@ import os from ..exceptions import ConfigurationError -from ..utils import ascii_dammit def configure(knobs, d, env_prefix, kwargs): @@ -43,11 +42,11 @@ def parse_conf_var(raw, from_unicode, context, name_in_context): value = raw extend = False try: - if isinstance(value, str): + if isinstance(value, bytes): value = value.decode('US-ASCII') return from_unicode(value), extend except UnicodeDecodeError as error: - value = ascii_dammit(value) + value = value.decode('US-ASCII', 'repr') error_detail = "Configuration values must be US-ASCII." except ValueError as error: error_detail = error.args[0] diff --git a/aspen/configuration/parse.py b/aspen/configuration/parse.py index 2524669c..340a4062 100644 --- a/aspen/configuration/parse.py +++ b/aspen/configuration/parse.py @@ -20,7 +20,7 @@ def identity(value): def media_type(media_type): # XXX for now. Read a spec - return media_type.encode('US-ASCII') + return media_type.encode('ascii').decode('ascii') def codec(value): codecs.lookup(value) @@ -28,9 +28,9 @@ def codec(value): def yes_no(s): s = s.lower() - if s in [u'yes', u'true', u'1']: + if s in ['yes', 'true', '1']: return True - if s in [u'no', u'false', u'0']: + if s in ['no', 'false', '0']: return False raise ValueError("must be either yes/true/1 or no/false/0") @@ -47,4 +47,4 @@ def renderer(value): if value not in RENDERERS: msg = "not one of {%s}" % (','.join(RENDERERS)) raise ValueError(msg) - return value.encode('US-ASCII') + return value diff --git a/aspen/http/request.py b/aspen/http/request.py index 8ad439c0..0a47ac2f 100644 --- a/aspen/http/request.py +++ b/aspen/http/request.py @@ -5,18 +5,21 @@ from __future__ import print_function from __future__ import unicode_literals - -import cgi -import urllib +from six import PY2, text_type as str +from six.moves.urllib.parse import parse_qs, unquote, unquote_plus from .mapping import Mapping +def _decode(o): + return o.decode('utf8') if isinstance(o, bytes) else o + + def path_decode(bs): - return urllib.unquote(bs).decode('UTF-8') + return _decode(unquote(bs.encode('ascii') if PY2 else bs)) -class PathPart(unicode): +class PathPart(str): """A string with a mapping for extra data about it.""" __slots__ = ['params'] @@ -40,17 +43,17 @@ def extract_rfc2396_params(path): * path should be raw so we don't split or operate on a decoded character * output is decoded """ - pathsegs = path.lstrip(b'/').split(b'/') + pathsegs = path.lstrip('/').split('/') segments_with_params = [] for component in pathsegs: - parts = component.split(b';') + parts = component.split(';') params = Mapping() segment = path_decode(parts[0]) for p in parts[1:]: - if b'=' in p: - k, v = p.split(b'=', 1) + if '=' in p: + k, v = p.split('=', 1) else: - k, v = p, b'' + k, v = p, '' params.add(path_decode(k), path_decode(v)) segments_with_params.append(PathPart(segment, params)) return segments_with_params @@ -59,7 +62,7 @@ def extract_rfc2396_params(path): def split_path_no_params(path): """This splits a path into parts on "/" only (no split on ";" or ","). """ - return [PathPart(path_decode(s)) for s in path.lstrip(b'/').split(b'/')] + return [PathPart(path_decode(s)) for s in path.lstrip('/').split('/')] class Path(Mapping): @@ -79,17 +82,14 @@ class Querystring(Mapping): def __init__(self, raw): """Takes a string of type application/x-www-form-urlencoded. """ - self.decoded = urllib.unquote_plus(raw).decode('UTF-8') + self.decoded = _decode(unquote_plus(raw)) self.raw = raw # parse_qs does its own unquote_plus'ing ... - as_dict = cgi.parse_qs( raw - , keep_blank_values = True - , strict_parsing = False - ) - - # ... but doesn't decode to unicode. - for k, vals in as_dict.items(): - as_dict[k.decode('UTF-8')] = [v.decode('UTF-8') for v in vals] + as_dict = parse_qs(raw, keep_blank_values=True, strict_parsing=False) + + # ... but doesn't decode to unicode (in older python versions). + for k, vals in list(as_dict.items()): + as_dict[_decode(k)] = [_decode(v) for v in vals] Mapping.__init__(self, as_dict) diff --git a/aspen/output.py b/aspen/output.py index 3525e95b..64a938fa 100644 --- a/aspen/output.py +++ b/aspen/output.py @@ -3,3 +3,7 @@ class Output(object): def __init__(self, **kw): self.__dict__.update(kw) + + @property + def text(self): + return self.body.decode(self.charset) if self.charset else None diff --git a/aspen/request_processor/algorithm.py b/aspen/request_processor/algorithm.py index 599c97ba..51a18dad 100644 --- a/aspen/request_processor/algorithm.py +++ b/aspen/request_processor/algorithm.py @@ -53,7 +53,7 @@ def dispatch_path_to_filesystem(request_processor, path, querystring): , uripath = path.decoded , startdir = request_processor.www_root ) - for k, v in result.wildcards.iteritems(): + for k, v in result.wildcards.items(): path[k] = v return {'dispatch_result': result} @@ -74,6 +74,6 @@ def render_resource(state, resource): def encode_output(output, request_processor): - if isinstance(output.body, unicode): + if not isinstance(output.body, bytes): output.charset = request_processor.charset_dynamic output.body = output.body.encode(output.charset) diff --git a/aspen/request_processor/dispatcher.py b/aspen/request_processor/dispatcher.py index 3165f56f..7dde1ad5 100644 --- a/aspen/request_processor/dispatcher.py +++ b/aspen/request_processor/dispatcher.py @@ -10,6 +10,7 @@ import os import posixpath from collections import namedtuple +from functools import reduce class DispatchError(Exception): diff --git a/aspen/request_processor/typecasting.py b/aspen/request_processor/typecasting.py index af3da595..00d531ef 100644 --- a/aspen/request_processor/typecasting.py +++ b/aspen/request_processor/typecasting.py @@ -28,7 +28,7 @@ def apply_typecasters(typecasters, path, state): *without* those extensions attached anymore, but with typecast values. It also then removes the string-value keys (the ones with the extensions). """ - for part in path.keys(): + for part in list(path.keys()): pieces = part.rsplit('.',1) if len(pieces) > 1: var, ext = pieces diff --git a/aspen/simplates/json_.py b/aspen/simplates/json_.py index cd757a62..ee66c375 100644 --- a/aspen/simplates/json_.py +++ b/aspen/simplates/json_.py @@ -70,7 +70,7 @@ def default(self, obj): cls = obj.__class__ # Use this instead of type(obj) because that # isn't consistent between new- and old-style # classes, and this is. - encode = encoders.get(cls, _json.JSONEncoder.default) + encode = encoders.get(cls, super(FriendlyEncoder, self).default) return encode(obj) def lazy_check(): diff --git a/aspen/simplates/pagination.py b/aspen/simplates/pagination.py index ab4da720..7d7fccfc 100644 --- a/aspen/simplates/pagination.py +++ b/aspen/simplates/pagination.py @@ -20,7 +20,7 @@ class Page(object): def __init__(self, content, header='', offset=0): self.content = content - self.header = header.decode('ascii') + self.header = header self.offset = offset @property diff --git a/aspen/simplates/renderers/__init__.py b/aspen/simplates/renderers/__init__.py index 25371ec9..52e910c7 100644 --- a/aspen/simplates/renderers/__init__.py +++ b/aspen/simplates/renderers/__init__.py @@ -105,9 +105,9 @@ def factories(configuration): try: capture = {} python_syntax = 'from aspen.simplates.renderers.%s import Factory' - exec python_syntax % name in capture + exec(python_syntax % name, capture) make_renderer = capture['Factory'](configuration) - except ImportError, err: + except ImportError as err: make_renderer = err err.info = sys.exc_info() renderer_factories[name] = make_renderer @@ -135,7 +135,7 @@ def __init__(self, factory, filepath, raw, media_type, offset): self.raw = raw self.media_type = media_type self.offset = offset - self.padded = (b'\n' * offset) + self.raw + self.padded = ('\n' * offset) + self.raw self.compiled = self.compile(self._filepath, self.padded) def __call__(self, context): diff --git a/aspen/simplates/renderers/json_dump.py b/aspen/simplates/renderers/json_dump.py index 6fbd0c26..3fec21a5 100644 --- a/aspen/simplates/renderers/json_dump.py +++ b/aspen/simplates/renderers/json_dump.py @@ -12,7 +12,10 @@ def render_content(self, context): output = context['output'] if not output.media_type: output.media_type = context['request_processor'].media_type_json - return json_.dumps(eval(self.compiled, globals(), context)) + r = json_.dumps(eval(self.compiled, globals(), context)) + if isinstance(r, bytes): + r = r.decode('ascii') + return r class Factory(Factory): diff --git a/aspen/simplates/simplate.py b/aspen/simplates/simplate.py index 93af7449..483cce63 100644 --- a/aspen/simplates/simplate.py +++ b/aspen/simplates/simplate.py @@ -12,17 +12,6 @@ renderer_re = re.compile(r'[a-z0-9.-_]+$') media_type_re = re.compile(r'[A-Za-z0-9.+*-]+/[A-Za-z0-9.+*-]+$') -MIN_PAGES=2 -MAX_PAGES=None - - -def _ordinal(n): - ords = [ 'zero' , 'one' , 'two', 'three', 'four' - , 'five', 'six', 'seven', 'eight', 'nine' ] - if 0 <= n < len(ords): - return ords[n] - return str(n) - def _decode(raw): """As per PEP 263, decode raw data according to the encoding specified in @@ -31,7 +20,7 @@ def _decode(raw): """ assert type(raw) is bytes # sanity check - decl_re = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)') + decl_re = re.compile(br'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)') def get_declaration(line): match = decl_re.match(line) @@ -51,7 +40,7 @@ def get_declaration(line): # observed behavior. encoding = potential - munged = b'# encoding set to {0}\n'.format(encoding) + munged = b'# encoding set to ' + encoding + b'\n' else: @@ -61,14 +50,15 @@ def get_declaration(line): # object, we'll get a SyntaxError if we have a well-formed # `coding: # ` line in it. - munged = b'# encoding NOT set to {0}\n'.format(potential) + munged = b'# encoding NOT set to ' + potential + b'\n' line = line.split(b'#')[0] + munged fulltext += line fulltext += sio.read() sio.close() - return fulltext.decode(encoding or b'ascii') + encoding = encoding.decode('ascii') if encoding else 'ascii' + return fulltext.decode(encoding) class SimplateDefaults(object): @@ -101,8 +91,8 @@ def __init__(self, defaults, fs, raw, default_media_type): self.defaults = defaults # type: SimplateDefaults self.fs = fs # type: str - self.raw = raw # type: str - self.decoded = _decode(raw) # type: unicode + self.raw = raw # type: bytes + self.decoded = _decode(raw) # type: str self.default_media_type = default_media_type # type: str self.renderers = {} # mapping of media type to Renderer objects @@ -158,7 +148,7 @@ def parse_into_pages(self, decoded): pages = list(split_and_escape(decoded)) npages = len(pages) - blank = [ Page(b'') ] + blank = [ Page('') ] if npages == 1: pages = blank + blank + pages @@ -271,15 +261,13 @@ def _get_renderer_factory(self, media_type, renderer): "renderers (might need third-party libs): %s.") raise SyntaxError(msg % (renderer, renderer_re.pattern, possible)) - renderer = renderer.decode('US-ASCII') - make_renderer = factories.get(renderer, None) if isinstance(make_renderer, ImportError): raise make_renderer elif make_renderer is None: possible = [] legend = '' - for k, v in sorted(factories.iteritems()): + for k, v in sorted(factories.items()): if isinstance(v, ImportError): k = '*' + k legend = " (starred are missing third-party libraries)" diff --git a/aspen/testing.py b/aspen/testing.py index b549d433..e1c919f5 100644 --- a/aspen/testing.py +++ b/aspen/testing.py @@ -66,7 +66,11 @@ def simple(self, contents='Greetings, program!', filepath='index.html.spt', urip """A helper to create a file and hit it through our machinery. """ if filepath is not None: - self.fs.www.mk((filepath, contents)) + if isinstance(contents, tuple): + contents, encoding = contents + else: + encoding = 'utf8' + self.fs.www.mk((filepath, contents, True, encoding)) if request_processor_configuration is not None: self.hydrate_request_processor(**request_processor_configuration) diff --git a/aspen/utils.py b/aspen/utils.py index 9c0574ea..e4e2c42b 100644 --- a/aspen/utils.py +++ b/aspen/utils.py @@ -11,44 +11,22 @@ # Register a 'repr' error strategy. # ================================= -# Sometimes we want to echo bytestrings back to a user, and we don't know what -# encoding will work. This error strategy replaces non-decodable bytes with -# their Python representation, so that they are human-visible. -# -# See also: -# - https://github.com/dcrosta/mongo/commit/e1ac732 -# - http://www.crummy.com/software/BeautifulSoup/bs4/doc/#unicode-dammit +# Before python 3.5 the 'backslashreplace' error handler only worked when +# encoding, not when decoding. The 'repr' handler below backports that +# bi-directionality to older python versions, including 2.7. def replace_with_repr(unicode_error): offender = unicode_error.object[unicode_error.start:unicode_error.end] - return (unicode(repr(offender).strip("'").strip('"')), unicode_error.end) + if isinstance(offender, bytes): + r = ''.join(r'\x{0:x}'.format(b if isinstance(b, int) else ord(b)) + for b in offender) + else: + r = offender.encode('ascii', 'backslashreplace').decode('ascii') + return (r, unicode_error.end) codecs.register_error('repr', replace_with_repr) -def unicode_dammit(s, encoding="UTF-8"): - """Given a bytestring, return a unicode decoded with `encoding`. - - Any bytes not decodable with UTF-8 will be replaced with their Python - representation, so you'll get something like u"foo\\xefbar". - - """ - if not isinstance(s, str): - raise TypeError("I got %s, but I want ." % s.__class__) - errors = 'repr' - return s.decode(encoding, errors) - - -def ascii_dammit(s): - """Given a bytestring, return a bytestring. - - The returned bytestring will have any non-ASCII bytes replaced with - their Python representation, so it will be pure ASCII. - - """ - return unicode_dammit(s, encoding="ASCII").encode("ASCII") - - # Filters # ======= # These are decorators for algorithm functions. @@ -85,7 +63,7 @@ def use_public_formatting(request): """ regex_res = [ (re.compile(regex), disposition) \ - for regex, disposition in regex_tuples.iteritems() ] + for regex, disposition in regex_tuples.items() ] def filter_function(function): def function_filter(request, *args): for regex, disposition in regex_res: diff --git a/build.py b/build.py index adbf9722..1bfa8eed 100644 --- a/build.py +++ b/build.py @@ -1,6 +1,7 @@ from __future__ import division, print_function, unicode_literals, with_statement import os +import shlex import sys import os.path from fabricate import ExecutionError, main, run, shell, autoclean @@ -14,6 +15,7 @@ 'algorithm>=1.0.0', 'filesystem_tree>=1.0.1', 'dependency_injection>=1.1.0', + 'six', ] TEST_DEPS = [ @@ -46,7 +48,19 @@ def _virt_version(envdir): def _env(envdir='env'): + d = __env(envdir) + # extend the PATH + path = os.path.join(d, 'Scripts' if os.name == "nt" else 'bin') + os.environ['PATH'] = path + os.pathsep + os.environ.get('PATH', '') + # install tox if it isn't there + try: + shell('pip', 'show', 'tox') + except ExecutionError: + run('pip', 'install', 'tox') + return d + +def __env(envdir): # http://stackoverflow.com/a/1883251 if hasattr(sys, 'real_prefix'): # We're already inside someone else's virtualenv. @@ -68,22 +82,18 @@ def env(): _env() -def _deps(envdir): - run(_virt('pip', envdir), 'install', *ASPEN_DEPS) +def _deps(): + shell('pip', 'install', *ASPEN_DEPS, ignore_status=False) -def _test_deps(envdir): - run(_virt('pip', envdir), 'install', *TEST_DEPS) +def _test_deps(): + _deps() + shell('pip', 'install', *TEST_DEPS, ignore_status=False) def _dev(envdir='env'): envdir = _env(envdir) - _deps(envdir) - _test_deps(envdir) - try: - shell(_virt('pip', envdir), 'show', 'aspen') - except ExecutionError: - run(_virt('pip', envdir), 'install', '--no-deps', '--editable', '.') + run('tox', '--notest', '--skip-missing-interpreters') return envdir @@ -103,7 +113,6 @@ def clean(): shell('find', '.', '-name', '*.pyc', '-delete') clean_env() clean_sphinx() - clean_jenv() clean_test() clean_build() @@ -111,27 +120,13 @@ def clean(): # Docs # ==== - -def docserve(): - """run the aspen website""" - envdir = _deps() - run(_virt('pip', envdir), 'install', 'aspen-tornado') - run(_virt('pip', envdir), 'install', 'pygments') - shell(_virt('python', envdir), '-m', 'aspen_io', silent=False) - - def _sphinx_cmd(packages, cmd): - envdir = _deps(envdir='denv') - for p in packages: - run(_virt('pip', envdir='denv'), 'install', p) - sphinxopts = [] + envdir = _env() + run('pip', 'install', *packages) builddir = 'docs/_build' run('mkdir', '-p', builddir) - newenv = os.environ - newenv.update({'PYTHONPATH': 'denv/lib/python2.7/site-packages'}) - args = ['-b', 'html', '-d', builddir + '/doctrees', sphinxopts, - 'docs', builddir + '/html'] - run(_virt(cmd, envdir=envdir), args, env=newenv) + args = ['-b', 'html', '-d', builddir + '/doctrees', 'docs', builddir + '/html'] + run(cmd, args) def sphinx(): """build sphinx documents""" @@ -150,20 +145,36 @@ def clean_sphinx(): # Testing # ======= -def pyflakes(): - shell(_virt('pyflakes', _dev()), 'aspen/', 'tests/', ignore_status=False, silent=False) +def _tox(*args, **kw): + _env() + kw.setdefault('silent', False) + shell('tox', '--skip-missing-interpreters', '--', *args, **kw) def test(): """run all tests""" - shell(_virt('py.test', _dev()), 'tests/', ignore_status=False, silent=False) - pyflakes() + # this calls tox, and tox calls the _test target below from inside each env + _tox(ignore_status=False) + + +def _test(pytest_args=()): + _test_deps() + pytest_args = pytest_args or shlex.split(os.environ.get('PYTEST_ARGS', '')) + shell('python', '-m', 'pytest', 'tests', *pytest_args, ignore_status=False, silent=False) + shell('pyflakes', 'aspen', 'tests', ignore_status=False, silent=False) def testf(): """run tests, stopping at the first failure""" - shell(_virt('py.test', _dev()), '-x', 'tests/', ignore_status=True, silent=False) - pyflakes() + _tox('python', 'build.py', '_testf', ignore_status=True) + + +def _testf(): + _test(pytest_args=['-x']) + + +def pyflakes(): + _tox('pyflakes', 'aspen', 'tests', ignore_status=False) def pylint(): @@ -176,28 +187,26 @@ def pylint(): def test_cov(): """run code coverage""" - run(_virt('py.test', _dev()), - '--junitxml=testresults.xml', - '--cov-report', 'term', - '--cov-report', 'xml', - '--cov-report', 'html', - '--cov', 'aspen', - 'tests/', - ignore_status=False) - pyflakes() + os.environ['PYTEST_ARGS'] = ( + '--junitxml=testresults.xml ' + '--cov-report term ' + '--cov-report xml ' + '--cov-report html ' + '--cov aspen' + ) + test() def analyse(): """run lint and coverage""" pylint() test_cov() - pyflakes() print('done!') def clean_test(): """clean test artifacts""" - clean_env() + shell('rm', '-rf', '.tox') shell('rm', '-rf', '.coverage', 'coverage.xml', 'testresults.xml', 'htmlcov', 'pylint.out') # Build @@ -219,45 +228,6 @@ def clean_build(): run('python', 'setup.py', 'clean', '-a') run('rm', '-rf', 'dist') -# Jython -# ====== -JYTHON_URL = "http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7-b1/jython-installer-2.7-b1.jar" - -def _jython_home(): - if not os.path.exists('jython_home'): - local_jython = 'jython-installer.jar' - run('wget', JYTHON_URL, '-qO', local_jython) - run('java', '-jar', local_jython, '-s', '-d', 'jython_home') - -def _jenv(): - _jython_home() - jenv = dict(os.environ) - jenv['PATH'] = os.path.join('.', 'jython_home', 'bin') + ':' + jenv['PATH'] - args = [ 'jython' ] + ENV_ARGS + [ '--python=jython', 'jenv' ] - run(*args, env=jenv) - -def clean_jenv(): - """clean up the jython environment""" - shell('find', '.', '-name', '*.class', '-delete') - shell('rm', '-rf', 'jenv', 'jython_home') - -def jython_test(): - """install jython and run tests with coverage (requires java)""" - _jenv() - run(_virt('pip', 'jenv'), 'install', *TEST_DEPS) - run(_virt('jython', 'jenv'), 'setup.py', 'develop') - run(_virt('jython', 'jenv'), _virt('py.test', 'jenv'), - '--junitxml=jython-testresults.xml', 'tests', - '--cov-report', 'term', - '--cov-report', 'xml', - '--cov', 'aspen', - ignore_status=True) - -def clean_jtest(): - """clean jython test results""" - shell('find', '.', '-name', '*.class', '-delete') - shell('rm', '-rf', 'jython-testresults.xml') - def show_targets(): """show the list of valid targets (this list)""" @@ -266,10 +236,8 @@ def show_targets(): targets = ['show_targets', None, 'env', 'dev', 'testf', 'test', 'pylint', 'test_cov', 'analyse', None, 'build', 'wheel', None, - 'docserve', 'sphinx', 'autosphinx', None, + 'sphinx', 'autosphinx', None, 'clean', 'clean_env', 'clean_test', 'clean_build', 'clean_sphinx', None, - 'jython_test', None, - 'clean_jenv', 'clean_jtest', None, ] #docs = '\n'.join([" %s - %s" % (t, LOCALS[t].__doc__) for t in targets]) #print(docs) diff --git a/tests/all-utf8.py b/tests/all-utf8.py index d07879c0..1f2a5bf8 100755 --- a/tests/all-utf8.py +++ b/tests/all-utf8.py @@ -6,9 +6,11 @@ import sys +from six import unichr + for i in range(2**16): u = unichr(i).encode('utf8') sys.stdout.write("%5d %s " % (i, u)) if i % 6 == 0: - print + print() diff --git a/tests/dispatch_table_test.py b/tests/dispatch_table_test.py index 510f3bef..045debe5 100644 --- a/tests/dispatch_table_test.py +++ b/tests/dispatch_table_test.py @@ -2,7 +2,9 @@ import os.path import posixpath + import pytest + from aspen.request_processor import dispatcher from aspen.testing import Harness @@ -36,7 +38,7 @@ def find_cols(defline, header_char='='): colends.append(len(defline)) break - return zip(colstarts,colends) + return list(zip(colstarts, colends)) def fields_from(dataline, cols): """ @@ -124,7 +126,6 @@ def test_all_table_entries(harness, files, request_uri, expected): inputfiles = headers[:answercol] requests = headers[answercol:] - for line in table[:3]: print(line) # We 'know' that table[0] == table[2], both header deflines, so skip down @@ -140,8 +141,8 @@ def test_all_table_entries(harness, files, request_uri, expected): realfiles = tuple([ f if f.endswith('/') else (f, GENERIC_SPT) for f in files ]) harness.fs.www.mk(*realfiles) for i,request_uri in enumerate(requests): - result = unicode(harness.simple(uripath=request_uri, filepath=None, want='response.code', raise_immediately=False)) - if result not in [ '404' ]: + result = str(harness.simple(uripath=request_uri, filepath=None, want='response.code', raise_immediately=False)) + if result != '404': state = harness.simple( uripath=request_uri , filepath=None , want='state' diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 62df41c4..8ff5ff51 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -68,7 +68,7 @@ def test_user_can_set_renderer_default(harness): """ harness.request_processor.renderer_default="stdlib_format" harness.fs.www.mk(('index.html.spt', SIMPLATE),) - actual = harness.simple(filepath=None, uripath='/', want='output.body') + actual = harness.simple(filepath=None, uripath='/', want='output.text') assert actual == 'Greetings, program!\n' def test_configuration_ignores_blank_indexfilenames(): @@ -81,54 +81,54 @@ def test_configuration_ignores_blank_indexfilenames(): # Tests of parsing perversities def test_parse_yes_no_yes_is_True(): - assert parse.yes_no(u'yEs') + assert parse.yes_no('yEs') def test_parse_yes_no_true_is_True(): - assert parse.yes_no(u'trUe') + assert parse.yes_no('trUe') def test_parse_yes_no_1_is_True(): - assert parse.yes_no(u'1') + assert parse.yes_no('1') def test_parse_yes_no_no_is_False(): - assert not parse.yes_no(u'nO') + assert not parse.yes_no('nO') def test_parse_yes_no_true_is_False(): - assert not parse.yes_no(u'FalSe') + assert not parse.yes_no('FalSe') def test_parse_yes_no_1_is_False(): - assert not parse.yes_no(u'0') + assert not parse.yes_no('0') def test_parse_yes_no_int_is_AttributeError(): raises(AttributeError, parse.yes_no, 1) def test_parse_yes_no_other_is_ValueError(): - raises(ValueError, parse.yes_no, u'cheese') + raises(ValueError, parse.yes_no, 'cheese') def test_parse_list_handles_one(): - actual = parse.list_(u'foo') + actual = parse.list_('foo') assert actual == ['foo'] def test_parse_list_handles_two(): - actual = parse.list_(u'foo,bar') + actual = parse.list_('foo,bar') assert actual == ['foo', 'bar'] def test_parse_list_handles_spaces(): - actual = parse.list_(u' foo , bar ') + actual = parse.list_(' foo , bar ') assert actual == ['foo', 'bar'] def test_parse_list_handles_some_spaces(): - actual = parse.list_(u'foo, bar, baz , buz ') + actual = parse.list_('foo, bar, baz , buz ') assert actual == ['foo', 'bar', 'baz', 'buz'] def test_parse_list_uniquifies(): - actual = parse.list_(u'foo,foo,bar') + actual = parse.list_('foo,foo,bar') assert actual == ['foo', 'bar'] def test_parse_renderer_good(): - actual = parse.renderer(u'stdlib_percent') - assert actual == u'stdlib_percent' + actual = parse.renderer('stdlib_percent') + assert actual == 'stdlib_percent' def test_parse_renderer_bad(): - raises(ValueError, parse.renderer, u'floober') + raises(ValueError, parse.renderer, 'floober') diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index eb2371c5..0e7f85db 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -31,7 +31,7 @@ def assert_virtvals(harness, uripath, expected_vals): assert actual == expected_vals def assert_body(harness, uripath, expected_body): - actual = harness.simple(filepath=None, uripath=uripath, want='output.body') + actual = harness.simple(filepath=None, uripath=uripath, want='output.text') assert actual == expected_body NEGOTIATED_SIMPLATE="""[-----] @@ -194,11 +194,11 @@ def test_virtual_path_is_virtual(harness): def test_virtual_path_sets_path(harness): harness.fs.www.mk(('%bar/foo.spt', NEGOTIATED_SIMPLATE),) - assert_virtvals(harness, '/blah/foo.html', {'bar': [u'blah']} ) + assert_virtvals(harness, '/blah/foo.html', {'bar': ['blah']} ) def test_virtual_path_sets_unicode_path(harness): harness.fs.www.mk(('%bar/foo.html', "Greetings, program!"),) - assert_virtvals(harness, b'/%E2%98%83/foo.html', {'bar': [u'\u2603']}) + assert_virtvals(harness, '/%E2%98%83/foo.html', {'bar': ['\u2603']}) def test_virtual_path_typecasts_to_int(harness): harness.fs.www.mk(('%year.int/foo.html', "Greetings, program!"),) @@ -238,11 +238,11 @@ def test_virtual_path_file_only_last_part____no_really(harness): def test_virtual_path_file_key_val_set(harness): harness.fs.www.mk(('foo/%bar.html.spt', NEGOTIATED_SIMPLATE),) - assert_virtvals(harness, '/foo/blah.html', {'bar': [u'blah']}) + assert_virtvals(harness, '/foo/blah.html', {'bar': ['blah']}) def test_virtual_path_file_key_val_not_cast(harness): harness.fs.www.mk(('foo/%bar.html.spt', NEGOTIATED_SIMPLATE),) - assert_virtvals(harness, '/foo/537.html', {'bar': [u'537']}) + assert_virtvals(harness, '/foo/537.html', {'bar': ['537']}) def test_virtual_path_file_key_val_cast(harness): harness.fs.www.mk(('foo/%bar.int.html.spt', NEGOTIATED_SIMPLATE),) @@ -250,7 +250,7 @@ def test_virtual_path_file_key_val_cast(harness): def test_virtual_path_file_key_val_percent(harness): harness.fs.www.mk(('foo/%bar.spt', NEGOTIATED_SIMPLATE),) - assert_virtvals(harness, '/foo/%25blah', {'bar': [u'%blah']}) + assert_virtvals(harness, '/foo/%25blah', {'bar': ['%blah']}) def test_virtual_path_file_not_dir(harness): harness.fs.www.mk( ('%foo/bar.html', "Greetings from bar!") @@ -423,13 +423,13 @@ def test_virtual_path_docs_6(harness): def test_virtual_path_parts_can_be_empty(harness): harness.fs.www.mk(('foo/%bar/index.html.spt', NEGOTIATED_SIMPLATE),) - assert_virtvals(harness, '/foo//' , {u'bar': [u'']}) + assert_virtvals(harness, '/foo//' , {'bar': ['']}) def test_file_matches_in_face_of_dir(harness): harness.fs.www.mk( ('%page/index.html.spt', NEGOTIATED_SIMPLATE) , ('%value.txt.spt', NEGOTIATED_SIMPLATE) ) - assert_virtvals(harness, '/baz.txt', {'value': [u'baz']}) + assert_virtvals(harness, '/baz.txt', {'value': ['baz']}) def test_file_matches_extension(harness): harness.fs.www.mk( ('%value.json.spt', '[-----]\n[-----]\n{"Greetings,": "program!"}') @@ -459,7 +459,7 @@ def test_file_with_no_extension_matches(harness): , ('value', '{"Greetings,": "program!"}') ) assert_fs(harness, '/baz', '%value.spt') - assert_virtvals(harness, '/baz', {'value': [u'baz']}) + assert_virtvals(harness, '/baz', {'value': ['baz']}) def test_dont_serve_hidden_files(harness): harness.fs.www.mk(('.secret_data', ''),) diff --git a/tests/test_dynamic_resource.py b/tests/test_dynamic_resource.py index 71444901..ed574c2c 100644 --- a/tests/test_dynamic_resource.py +++ b/tests/test_dynamic_resource.py @@ -121,7 +121,7 @@ def test_get_renderer_factory_can_raise_syntax_error(get): def test_render_is_happy_not_to_negotiate(harness): output = harness.simple(filepath='index.spt', contents=SIMPLATE) - assert output.body == "Greetings, program!\n" + assert output.text == "Greetings, program!\n" def test_render_sets_media_type_when_it_doesnt_negotiate(harness): output = harness.simple(filepath='index.spt', contents=SIMPLATE) @@ -129,15 +129,15 @@ def test_render_sets_media_type_when_it_doesnt_negotiate(harness): def test_render_is_happy_not_to_negotiate_with_defaults(harness): output = harness.simple(filepath='index.spt', contents="[---]\n[---]\nGreetings, program!\n") - assert output.body == "Greetings, program!\n" + assert output.text == "Greetings, program!\n" def test_render_negotiates(harness): output = harness.simple(filepath='index.spt', contents=SIMPLATE, accept_header='text/html') - assert output.body == "

Greetings, program!

\n" + assert output.text == "

Greetings, program!

\n" def test_ignores_busted_accept(harness): output = harness.simple(filepath='index.spt', contents=SIMPLATE, accept_header='text/html;') - assert output.body == "Greetings, program!\n" + assert output.text == "Greetings, program!\n" def test_render_sets_media_type_when_it_negotiates(harness): output = harness.simple(filepath='index.spt', contents=SIMPLATE, accept_header='text/html') @@ -188,12 +188,12 @@ def test_can_override_default_renderers_by_mimetype(harness): install_glubber(harness) harness.fs.www.mk(('index.spt', SIMPLATE),) output = harness.simple(filepath='index.spt', contents=SIMPLATE, accept_header='text/plain') - assert output.body == "glubber" + assert output.text == "glubber" def test_can_override_default_renderer_entirely(harness): install_glubber(harness) output = harness.simple(filepath='index.spt', contents=SIMPLATE, accept_header='text/plain') - assert output.body == "glubber" + assert output.text == "glubber" # indirect @@ -207,15 +207,15 @@ def test_can_override_default_renderer_entirely(harness): Greetings, %(foo)s!""" def test_indirect_negotiation_sets_media_type(harness): - response = harness.simple(INDIRECTLY_NEGOTIATED_SIMPLATE, '/foo.spt', '/foo.html') + output = harness.simple(INDIRECTLY_NEGOTIATED_SIMPLATE, '/foo.spt', '/foo.html') expected = "

Greetings, program!

\n" - actual = response.body + actual = output.text assert actual == expected def test_indirect_negotiation_sets_media_type_to_secondary(harness): - response = harness.simple(INDIRECTLY_NEGOTIATED_SIMPLATE, '/foo.spt', '/foo.txt') + output = harness.simple(INDIRECTLY_NEGOTIATED_SIMPLATE, '/foo.spt', '/foo.txt') expected = "Greetings, program!" - actual = response.body + actual = output.text assert actual == expected def test_indirect_negotiation_with_unsupported_media_type_is_an_error(harness): @@ -233,9 +233,9 @@ def test_indirect_negotiation_with_unsupported_media_type_is_an_error(harness): def test_dynamic_resource_inside_virtual_path(harness): - response = harness.simple(SIMPLATE_VIRTUAL_PATH, '/%foo/bar.spt', '/program/bar.txt') + output = harness.simple(SIMPLATE_VIRTUAL_PATH, '/%foo/bar.spt', '/program/bar.txt') expected = "Greetings, program!" - actual = response.body + actual = output.text assert actual == expected SIMPLATE_STARTYPE = """\ @@ -249,18 +249,18 @@ def test_dynamic_resource_inside_virtual_path(harness): Greetings, %(foo)s!""" def test_dynamic_resource_inside_virtual_path_with_startypes_present(harness): - response = harness.simple(SIMPLATE_STARTYPE, '/%foo/bar.spt', '/program/bar.html') - actual = response.body + output = harness.simple(SIMPLATE_STARTYPE, '/%foo/bar.spt', '/program/bar.html') + actual = output.text assert '

' in actual def test_dynamic_resource_inside_virtual_path_with_startype_partial_match(harness): - response = harness.simple(SIMPLATE_STARTYPE, '/%foo/bar.spt', '/program/bar.txt') + output = harness.simple(SIMPLATE_STARTYPE, '/%foo/bar.spt', '/program/bar.txt') expected = "Greetings, program!" - actual = response.body + actual = output.text assert actual == expected def test_dynamic_resource_inside_virtual_path_with_startype_fallback(harness): - response = harness.simple(SIMPLATE_STARTYPE, '/%foo/bar.spt', '/program/bar.jpg') + output = harness.simple(SIMPLATE_STARTYPE, '/%foo/bar.spt', '/program/bar.jpg') expected = "Unknown request type, program!" - actual = response.body.strip() + actual = output.text.strip() assert actual == expected diff --git a/tests/test_json_resource.py b/tests/test_json_resource.py index 4e017e91..97e9b7ec 100644 --- a/tests/test_json_resource.py +++ b/tests/test_json_resource.py @@ -3,7 +3,7 @@ from __future__ import print_function from __future__ import unicode_literals -import StringIO +import io from pytest import raises @@ -16,7 +16,7 @@ def test_json_basically_works(harness): }''' actual = harness.simple( "[---]\n[---] application/json\n{'Greetings': 'program!'}" , filepath="foo.json.spt" - ).body + ).text assert actual == expected def test_json_defaults_to_application_json_for_static_json(harness): @@ -62,6 +62,7 @@ def test_json_handles_unicode(harness): "Greetings": "\u00b5" }''' actual = harness.simple(''' + from six import unichr [---] [---] application/json {'Greetings': unichr(181)} @@ -69,9 +70,9 @@ def test_json_handles_unicode(harness): assert actual == expected def test_json_doesnt_handle_non_ascii_bytestrings(harness): - raises( UnicodeDecodeError + raises( (TypeError, UnicodeDecodeError) , harness.simple - , "[---]\n[---] application/json\n{'Greetings': chr(181)}" + , "[---]\n[---] application/json\n{'Greetings': chr(181).encode('utf8')}" , filepath="foo.json.spt" ) @@ -84,7 +85,7 @@ def test_json_handles_time(harness): import datetime [---------------] application/json {'seen': datetime.time(19, 30)} - ''', filepath="foo.json.spt").body + ''', filepath="foo.json.spt").text assert actual == expected def test_json_handles_date(harness): @@ -97,7 +98,7 @@ def test_json_handles_date(harness): import datetime [---------------] application/json {'created': datetime.date(2011, 5, 9)} - ''', filepath='foo.json.spt').body + ''', filepath='foo.json.spt').text assert actual == expected def test_json_handles_datetime(harness): @@ -109,7 +110,7 @@ def test_json_handles_datetime(harness): import datetime [---------------] application/json {'timestamp': datetime.datetime(2011, 5, 9, 0, 0)} - """, filepath="foo.json.spt").body + """, filepath="foo.json.spt").text assert actual == expected def test_json_handles_complex(harness): @@ -121,7 +122,7 @@ def test_json_handles_complex(harness): }''' actual = harness.simple( "[---]\n[---] application/json\n{'complex': complex(1,2)}" , filepath="foo.json.spt" - ).body + ).text # The json module puts trailing spaces after commas, but simplejson # does not. Normalize the actual input to work around that. actual = '\n'.join([line.rstrip() for line in actual.split('\n')]) @@ -135,14 +136,14 @@ def test_json_raises_TypeError_on_unknown_types(harness): ) def test_aspen_json_load_loads(): - fp = StringIO.StringIO() + fp = io.StringIO() fp.write('{"cheese": "puffs"}') fp.seek(0) actual = json_.load(fp) assert actual == {'cheese': 'puffs'} def test_aspen_json_dump_dumps(): - fp = StringIO.StringIO() + fp = io.BytesIO() if str is bytes else io.StringIO() json_.dump({"cheese": "puffs"}, fp) fp.seek(0) actual = fp.read() @@ -170,7 +171,7 @@ def test_aspen_json_dumps_dumps(): });''' def _jsonp_query(harness, querystring): - return harness.simple(JSONP_SIMPLATE, filepath='index.json.spt', querystring=querystring).body + return harness.simple(JSONP_SIMPLATE, filepath='index.json.spt', querystring=querystring).text def test_jsonp_basically_works(harness): actual = _jsonp_query(harness, "jsonp=foo") @@ -184,7 +185,7 @@ def test_jsonp_defaults_to_json_with_no_callback(harness): expected = '''{ "Greetings": "program!" }''' - actual = harness.simple(JSONP_SIMPLATE, filepath='index.spt').body + actual = harness.simple(JSONP_SIMPLATE, filepath='index.spt').text assert actual == expected, "wanted %r got %r " % (expected, actual) def test_jsonp_filters_disallowed_chars(harness): diff --git a/tests/test_mappings.py b/tests/test_mappings.py index 70ae2018..85489f08 100644 --- a/tests/test_mappings.py +++ b/tests/test_mappings.py @@ -113,7 +113,7 @@ def test_mapping_pop_removes_the_item_if_that_was_the_last_value(): m['foo'] = 1 m.pop('foo') expected = [] - actual = m.keys() + actual = list(m.keys()) assert actual == expected def test_mapping_pop_raises_KeyError_by_default(): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 64f05837..78e9e88e 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -3,6 +3,8 @@ from __future__ import print_function from __future__ import unicode_literals +from six import text_type as str + from pytest import raises from aspen.simplates import json_ @@ -17,7 +19,7 @@ def compile(self, *a): def render_content(self, context): d = dict((k, v) for k, v in self.__dict__.items() if k[0] != '_') - return json_.dumps(d) + return str(json_.dumps(d)) class TestFactory(Factory): Renderer = TestRenderer @@ -29,7 +31,8 @@ def compile_meta(self, configuration): request_processor.renderer_factories['lorem'] = TestFactory(request_processor) r = harness.simple("[---]\n[---] text/html via lorem\nLorem ipsum") - d = json_.loads(r.body) + assert r.text + d = json_.loads(r.text) assert d['meta'] == 'foobar' assert d['raw'] == 'Lorem ipsum' assert d['media_type'] == 'text/html' @@ -41,11 +44,11 @@ def test_renderer_padding_works_with_padded_output(harness): class TestRenderer(Renderer): def compile(self, filepath, padded): - assert padded[:self.offset] == b'\n' * self.offset + assert padded[:self.offset] == '\n' * self.offset return padded def render_content(self, context): - return b'\n' + self.compiled + b'\n' + return '\n' + self.compiled + '\n' class TestFactory(Factory): Renderer = TestRenderer @@ -54,18 +57,18 @@ class TestFactory(Factory): request_processor.renderer_factories['x'] = TestFactory(request_processor) output = harness.simple("[---]\n[---] text/plain via x\nSome text") - assert output.body == '\nSome text\n' + assert output.text == '\nSome text\n' def test_renderer_padding_works_with_stripped_output(harness): class TestRenderer(Renderer): def compile(self, filepath, padded): - assert padded[:self.offset] == b'\n' * self.offset + assert padded[:self.offset] == '\n' * self.offset return padded def render_content(self, context): - return b'\n' + self.raw + b'\n' + return '\n' + self.raw + '\n' class TestFactory(Factory): Renderer = TestRenderer @@ -74,7 +77,7 @@ class TestFactory(Factory): request_processor.renderer_factories['y'] = TestFactory(request_processor) output = harness.simple("[---]\n[---] text/plain via y\nSome text") - assert output.body == '\nSome text\n' + assert output.text == '\nSome text\n' def test_renderer_padding_achieves_correct_line_numbers_in_tracebacks(harness): diff --git a/tests/test_request_processor.py b/tests/test_request_processor.py index 6c256b21..7ed2d46b 100644 --- a/tests/test_request_processor.py +++ b/tests/test_request_processor.py @@ -8,13 +8,6 @@ from aspen.request_processor import RequestProcessor -simple_error_spt = """ -[---] -[---] text/plain via stdlib_format -{response.body} -""" - - def test_basic(): rp = RequestProcessor() expected = os.getcwd() @@ -23,16 +16,16 @@ def test_basic(): def test_processor_can_process(harness): output = harness.simple('[---]\n[---]\nGreetings, program!', 'index.html.spt') - assert output.body == 'Greetings, program!' + assert output.text == 'Greetings, program!' def test_user_can_influence_render_context_via_algorithm_state(harness): def add_foo_to_context(path): return {'foo': 'bar'} harness.request_processor.algorithm.insert_after('dispatch_path_to_filesystem', add_foo_to_context) - assert harness.simple('[---]\n[---]\n%(foo)s', 'index.html.spt').body == 'bar' + assert harness.simple('[---]\n[---]\n%(foo)s', 'index.html.spt').text == 'bar' def test_resources_can_import_from_project_root(harness): harness.fs.project.mk(('foo.py', 'bar = "baz"')) assert harness.simple( "from foo import bar\n[---]\n[---]\nGreetings, %(bar)s!" , 'index.html.spt' - , raise_immediately=False).body == "Greetings, baz!" + , raise_immediately=False).text == "Greetings, baz!" diff --git a/tests/test_resources.py b/tests/test_resources.py index e6b63fa6..78f6eff7 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -16,7 +16,7 @@ def test_barely_working(harness): assert output.media_type == 'text/html' def test_charset_static_barely_working(harness): - output = harness.simple( 'Greetings, program!'.encode('utf16') + output = harness.simple( ('Greetings, program!', 'utf16') , 'index.html' , request_processor_configuration={'charset_static': 'utf16'} ) @@ -24,7 +24,7 @@ def test_charset_static_barely_working(harness): assert output.charset == 'utf16' def test_charset_static_checks_encoding(harness): - output = harness.simple( 'Greetings, program!'.encode('utf16') + output = harness.simple( ('Greetings, program!', 'utf16') , 'index.html' , request_processor_configuration={'charset_static': 'utf8'} ) @@ -48,7 +48,7 @@ def test_charset_dynamic_barely_working(harness): assert output.charset == 'ascii' def test_resource_pages_work(harness): - actual = harness.simple("[---]\nfoo = 'bar'\n[--------]\nGreetings, %(foo)s!").body + actual = harness.simple("[---]\nfoo = 'bar'\n[--------]\nGreetings, %(foo)s!").text assert actual == "Greetings, bar!" def test_resource_dunder_all_limits_vars(harness): @@ -70,7 +70,7 @@ def test_path_part_params_are_available(harness): [---] %(a)s """, '/foo/index.html.spt', '/foo;a=1;b;a=3/') - assert output.body == "3\n" + assert output.text == "3\n" def test_resources_dont_leak_whitespace(harness): """This aims to resolve https://github.com/whit537/aspen/issues/8. @@ -79,7 +79,7 @@ def test_resources_dont_leak_whitespace(harness): [--------------] foo = [1,2,3,4] [--------------] - %(foo)r""").body + %(foo)r""").text assert actual == "[1, 2, 3, 4]" def test_negotiated_resource_doesnt_break(harness): @@ -92,7 +92,7 @@ def test_negotiated_resource_doesnt_break(harness): [-----------] text/html

Greetings, %(foo)s!

""" - , filepath='index.spt').body + , filepath='index.spt').text assert actual == expected def test_request_processor_is_in_context(harness): @@ -101,7 +101,7 @@ def test_request_processor_is_in_context(harness): [--------] [--------] It worked.""") - assert output.body == 'It worked.' + assert output.text == 'It worked.' def test_unknown_mimetype_yields_default_mimetype(harness): output = harness.simple( 'Greetings, program!' @@ -111,7 +111,7 @@ def test_unknown_mimetype_yields_default_mimetype(harness): def test_templating_without_script_works(harness): output = harness.simple('[-----]\n[-----] via stdlib_format\n{path.raw}') - assert output.body == '/' + assert output.text == '/' # Test offset calculation diff --git a/tests/test_simplates.py b/tests/test_simplates.py index 52a908f1..976fee27 100644 --- a/tests/test_simplates.py +++ b/tests/test_simplates.py @@ -9,11 +9,11 @@ def test_default_media_type_works(harness): - response = harness.simple(""" + output = harness.simple(""" [---] [---] plaintext""", raise_immediately=False) - assert "plaintext" in response.body + assert "plaintext" in output.text SIMPLATE=""" foo = %s @@ -21,9 +21,8 @@ def test_default_media_type_works(harness): {foo}""" def test_can_use_path(harness): - response = harness.simple( SIMPLATE % "path.raw" - ) - assert response.body == '/' + output = harness.simple(SIMPLATE % "path.raw") + assert output.text == '/' def test_cant_implicitly_override_state(harness): @@ -33,7 +32,7 @@ def test_cant_implicitly_override_state(harness): "{resource}", want='state' ) - assert state['output'].body == 'foo' + assert state['output'].text == 'foo' assert state['resource'] != 'foo' @@ -54,51 +53,51 @@ def test_can_explicitly_override_state(harness): def test_but_python_sections_exhibit_module_scoping_behavior(harness): - response = harness.simple("""[---] + output = harness.simple("""[---] bar = 'baz' def foo(): return bar foo = foo() [---] text/html via stdlib_format {foo}""") - assert response.body == 'baz' + assert output.text == 'baz' def test_one_page_works(harness): - response = harness.simple("Template") - assert response.body == 'Template' + output = harness.simple("Template") + assert output.text == 'Template' def test_two_pages_works(harness): - response = harness.simple(SIMPLATE % "'Template'") - assert response.body == 'Template' + output = harness.simple(SIMPLATE % "'Template'") + assert output.text == 'Template' def test_three_pages_one_python_works(harness): - response = harness.simple(""" + output = harness.simple(""" foo = 'Template' [---] text/plain via stdlib_format {foo} [---] text/xml {foo}""", filepath='index.spt') - assert response.body.strip() == 'Template' + assert output.text.strip() == 'Template' def test_three_pages_two_python_works(harness): - response = harness.simple("""[---] + output = harness.simple("""[---] python_code = True [---] Template""") - assert response.body == 'Template' + assert output.text == 'Template' # _decode def test_decode_can_take_encoding_from_first_line(): - actual = _decode(b"""\ + actual = _decode("""\ # -*- coding: utf8 -*- text = u'א' - """) + """.encode('utf8')) expected = """\ # encoding set to utf8 text = u'א' @@ -106,11 +105,11 @@ def test_decode_can_take_encoding_from_first_line(): assert actual == expected def test_decode_can_take_encoding_from_second_line(): - actual = _decode(b"""\ + actual = _decode("""\ #!/blah/blah # -*- coding: utf8 -*- text = u'א' - """) + """.encode('utf8')) expected = """\ #!/blah/blah # encoding set to utf8 @@ -119,11 +118,11 @@ def test_decode_can_take_encoding_from_second_line(): assert actual == expected def test_decode_prefers_first_line_to_second(): - actual = _decode(b"""\ + actual = _decode("""\ # -*- coding: utf8 -*- # -*- coding: ascii -*- text = u'א' - """) + """.encode('utf8')) expected = """\ # encoding set to utf8 # encoding NOT set to ascii @@ -132,12 +131,12 @@ def test_decode_prefers_first_line_to_second(): assert actual == expected def test_decode_ignores_third_line(): - actual = _decode(b"""\ + actual = _decode("""\ # -*- coding: utf8 -*- # -*- coding: ascii -*- # -*- coding: cornnuts -*- text = u'א' - """) + """.encode('utf8')) expected = """\ # encoding set to utf8 # encoding NOT set to ascii @@ -147,19 +146,19 @@ def test_decode_ignores_third_line(): assert actual == expected def test_decode_can_take_encoding_from_various_line_formats(): - formats = [ b'-*- coding: utf8 -*-' - , b'-*- encoding: utf8 -*-' - , b'coding: utf8' - , b' coding: utf8' - , b'\tencoding: utf8' - , b'\t flubcoding=utf8' + formats = [ '-*- coding: utf8 -*-' + , '-*- encoding: utf8 -*-' + , 'coding: utf8' + , ' coding: utf8' + , '\tencoding: utf8' + , '\t flubcoding=utf8' ] for fmt in formats: def test(): - actual = _decode(b"""\ + actual = _decode("""\ # {0} text = u'א' - """.format(fmt)) + """.format(fmt).encode('utf8')) expected = """\ # encoding set to utf8 text = u'א' @@ -168,18 +167,18 @@ def test(): yield test def test_decode_cant_take_encoding_from_bad_line_formats(): - formats = [ b'-*- coding : utf8 -*-' - , b'foo = 0 -*- encoding: utf8 -*-' - , b' coding : utf8' - , b'encoding : utf8' - , b' flubcoding =utf8' - , b'coding: ' + formats = [ '-*- coding : utf8 -*-' + , 'foo = 0 -*- encoding: utf8 -*-' + , ' coding : utf8' + , 'encoding : utf8' + , ' flubcoding =utf8' + , 'coding: ' ] for fmt in formats: def test(): - raw = b"""\ + raw = """\ # {0} text = u'א' - """.format(fmt) + """.format(fmt).encode('utf8') raises(UnicodeDecodeError, _decode, raw) yield test diff --git a/tests/test_unicode.py b/tests/test_unicode.py index 8ec36954..6f716c18 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -9,32 +9,32 @@ def test_non_ascii_bytes_fail_without_encoding(harness): - raises(LoadError, harness.simple, b""" + raises(LoadError, harness.simple, (""" [------------------] text = u'א' [------------------] %(text)s - """) + """, 'utf8')) def test_non_ascii_bytes_work_with_encoding(harness): - expected = u'א'.encode('utf8') - actual = harness.simple(b""" + expected = 'א'.encode('utf8') + actual = harness.simple((""" # encoding=utf8 [------------------] text = u'א' [------------------] %(text)s - """).body.strip() + """, 'utf8')).body.strip() assert actual == expected def test_the_exec_machinery_handles_two_encoding_lines_properly(harness): - expected = u'א'.encode('utf8') - actual = harness.simple(b"""\ + expected = 'א'.encode('utf8') + actual = harness.simple(("""\ # encoding=utf8 # encoding=ascii [------------------] text = u'א' [------------------] %(text)s - """).body.strip() + """, 'utf8')).body.strip() assert actual == expected diff --git a/tests/test_utils.py b/tests/test_utils.py index 335251aa..35e5c8ba 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,36 +6,22 @@ from pytest import raises # Note: importing from `aspen.utils` installs the 'repr' error strategy -from aspen.utils import ascii_dammit, unicode_dammit +from aspen.utils import replace_with_repr -GARBAGE = b"\xef\xf9" +replace_with_repr # shut up pyflakes + +GARBAGE = b"a\xef\xf9" def test_garbage_is_garbage(): raises(UnicodeDecodeError, lambda s: s.decode('utf8'), GARBAGE) -def test_repr_error_strategy_works(): +def test_repr_error_strategy_works_when_decoding(): errors = 'repr' actual = GARBAGE.decode('utf8', errors) - assert actual == r"\xef\xf9" - -def test_unicode_dammit_works(): - actual = unicode_dammit(b"foo\xef\xfar") - assert actual == r"foo\xef\xfar" - -def test_unicode_dammit_fails(): - raises(TypeError, unicode_dammit, 1) - raises(TypeError, unicode_dammit, []) - raises(TypeError, unicode_dammit, {}) + assert actual == r"a\xef\xf9" -def test_unicode_dammit_decodes_utf8(): - actual = unicode_dammit(b"comet: \xe2\x98\x84") - assert actual == u"comet: \u2604" - -def test_unicode_dammit_takes_encoding(): - actual = unicode_dammit(b"comet: \xe2\x98\x84", encoding="ASCII") - assert actual == r"comet: \xe2\x98\x84" - -def test_ascii_dammit_works(): - actual = ascii_dammit(b"comet: \xe2\x98\x84") - assert actual == r"comet: \xe2\x98\x84" +def test_repr_error_strategy_works_when_encoding(): + errors = 'repr' + actual = 'comet: \u2604'.encode('ascii', errors) + assert actual == br"comet: \u2604" diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..98dc2666 --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist = py27,py34,py35 + +[testenv] +commands = {posargs:python build.py _test} +passenv = PYTEST_ARGS +usedevelop = True