Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

now works with 2to3, passes all the test in py25-27,31,32

  • Loading branch information...
commit 3c4cf2f13207a9df6dfc0da34a4db82098b0166a 1 parent 105996c
Kyung-hown Chung authored
Showing with 800 additions and 467 deletions.
  1. +6 −1 setup.py
  2. +10 −0 tox.ini
  3. +2 −2 werkzeug/__init__.py
  4. +15 −4 werkzeug/_internal.py
  5. +9 −5 werkzeug/contrib/cache.py
  6. +24 −17 werkzeug/contrib/securecookie.py
  7. +4 −3 werkzeug/contrib/sessions.py
  8. +5 −0 werkzeug/contrib/wrappers.py
  9. +28 −19 werkzeug/datastructures.py
  10. +6 −1 werkzeug/debug/console.py
  11. +33 −10 werkzeug/debug/repr.py
  12. +8 −3 werkzeug/debug/tbtools.py
  13. +2 −0  werkzeug/exceptions.py
  14. +30 −15 werkzeug/formparser.py
  15. +39 −12 werkzeug/http.py
  16. +41 −25 werkzeug/routing.py
  17. +10 −0 werkzeug/security.py
  18. +11 −4 werkzeug/serving.py
  19. +38 −15 werkzeug/test.py
  20. +6 −4 werkzeug/testapp.py
  21. +1 −0  werkzeug/testsuite/__init__.py
  22. +2 −1  werkzeug/testsuite/compat.py
  23. +3 −2 werkzeug/testsuite/contrib/cache.py
  24. +10 −9 werkzeug/testsuite/contrib/fixers.py
  25. +2 −1  werkzeug/testsuite/contrib/securecookie.py
  26. +2 −1  werkzeug/testsuite/contrib/wrappers.py
  27. +23 −22 werkzeug/testsuite/datastructures.py
  28. +22 −6 werkzeug/testsuite/debug.py
  29. +4 −3 werkzeug/testsuite/exceptions.py
  30. +74 −67 werkzeug/testsuite/formparser.py
  31. +8 −8 werkzeug/testsuite/http.py
  32. +4 −3 werkzeug/testsuite/internal.py
  33. +2 −1  werkzeug/testsuite/routing.py
  34. +11 −7 werkzeug/testsuite/serving.py
  35. +34 −23 werkzeug/testsuite/test.py
  36. +4 −2 werkzeug/testsuite/urls.py
  37. +5 −4 werkzeug/testsuite/utils.py
  38. +46 −39 werkzeug/testsuite/wrappers.py
  39. +59 −55 werkzeug/testsuite/wsgi.py
  40. +107 −44 werkzeug/urls.py
  41. +9 −4 werkzeug/utils.py
  42. +17 −6 werkzeug/wrappers.py
  43. +24 −19 werkzeug/wsgi.py
7 setup.py
View
@@ -57,8 +57,12 @@
from setuptools import setup
except ImportError:
from distutils.core import setup
+import sys
+kwargs = {}
+if sys.version_info >= (3, ):
+ kwargs['use_2to3'] = True
setup(
name='Werkzeug',
version='0.9-dev',
@@ -83,5 +87,6 @@
include_package_data=True,
test_suite='werkzeug.testsuite.suite',
zip_safe=False,
- platforms='any'
+ platforms='any',
+ **kwargs
)
10 tox.ini
View
@@ -0,0 +1,10 @@
+[tox]
+envlist=py25,py26,py27,py31,py32
+
+[testenv]
+changedir=envtmpdir
+commands={envpython} run-tests.py
+
+[testenv:py25]
+deps=
+ simplejson
4 werkzeug/__init__.py
View
@@ -106,8 +106,8 @@
object_origins = {}
-for module, items in all_by_module.iteritems():
- for item in items:
+for module in all_by_module:
+ for item in all_by_module[module]:
object_origins[item] = module
19 werkzeug/_internal.py
View
@@ -9,15 +9,23 @@
:license: BSD, see LICENSE for more details.
"""
import inspect
+import sys
from weakref import WeakKeyDictionary
-from cStringIO import StringIO
+try:
+ from io import BytesIO
+except ImportError:
+ from cStringIO import StringIO as BytesIO
from Cookie import SimpleCookie, Morsel, CookieError
from time import gmtime
from datetime import datetime, date
+if sys.version_info >= (3, ):
+ _b = lambda _: _ if isinstance(_, bytes) else _.encode('utf-8')
+else:
+ _b = str
_logger = None
-_empty_stream = StringIO('')
+_empty_stream = BytesIO(_b(''))
_signature_cache = WeakKeyDictionary()
_epoch_ord = date(1970, 1, 1).toordinal()
@@ -344,7 +352,10 @@ def __repr__(self):
def _easteregg(app):
"""Like the name says. But who knows how it works?"""
- gyver = '\n'.join([x + (77 - len(x)) * ' ' for x in '''
+ from base64 import b64decode
+ from zlib import decompress
+ gyver = _b('\n').join([x + (77 - len(x)) * _b(' ') for x in
+ decompress(b64decode(_b('''
eJyFlzuOJDkMRP06xRjymKgDJCDQStBYT8BCgK4gTwfQ2fcFs2a2FzvZk+hvlcRvRJD148efHt9m
9Xz94dRY5hGt1nrYcXx7us9qlcP9HHNh28rz8dZj+q4rynVFFPdlY4zH873NKCexrDM6zxxRymzz
4QIxzK4bth1PV7+uHn6WXZ5C4ka/+prFzx3zWLMHAVZb8RRUxtFXI5DTQ2n3Hi2sNI+HK43AOWSY
@@ -375,7 +386,7 @@ def _easteregg(app):
krEDuNoJCHNlZYhKpvw4mspVWxqo415n8cD62N9+EfHrAvqQnINStetek7RY2Urv8nxsnGaZfRr/
nhXbJ6m/yl1LzYqscDZA9QHLNbdaSTTr+kFg3bC0iYbX/eQy0Bv3h4B50/SGYzKAXkCeOLI3bcAt
mj2Z/FM1vQWgDynsRwNvrWnJHlespkrp8+vO1jNaibm+PhqXPPv30YwDZ6jApe3wUjFQobghvW9p
-7f2zLkGNv8b191cD/3vs9Q833z8t'''.decode('base64').decode('zlib').splitlines()])
+7f2zLkGNv8b191cD/3vs9Q833z8t'''))).splitlines()])
def easteregged(environ, start_response):
def injecting_start_response(status, headers, exc_info=None):
headers.append(('X-Powered-By', 'Werkzeug'))
14 werkzeug/contrib/cache.py
View
@@ -66,6 +66,7 @@ def get_sidebar(user):
from itertools import izip
from time import time
from werkzeug.posixemulation import rename
+from werkzeug._internal import _b
try:
import cPickle as pickle
@@ -84,8 +85,11 @@ def _items(mappingorseq):
... assert k*k == v
"""
- return mappingorseq.iteritems() if hasattr(mappingorseq, 'iteritems') \
- else mappingorseq
+ if hasattr(mappingorseq, 'items'):
+ return mappingorseq.items()
+ if hasattr(mappingorseq, 'iteritems'):
+ return mappingorseq.iteritems()
+ return mappingorseq
class BaseCache(object):
@@ -495,7 +499,7 @@ def dump_object(self, value):
t = type(value)
if t is int or t is long:
return str(value)
- return '!' + pickle.dumps(value)
+ return _b('!') + pickle.dumps(value)
def load_object(self, value):
"""The reversal of :meth:`dump_object`. This might be callde with
@@ -503,7 +507,7 @@ def load_object(self, value):
"""
if value is None:
return None
- if value.startswith('!'):
+ if value.startswith(_b('!')):
return pickle.loads(value[1:])
try:
return int(value)
@@ -629,7 +633,7 @@ def clear(self):
pass
def _get_filename(self, key):
- hash = md5(key).hexdigest()
+ hash = md5(_b(key)).hexdigest()
return os.path.join(self._path, hash)
def get(self, key):
41 werkzeug/contrib/securecookie.py
View
@@ -89,12 +89,18 @@ def application(environ, start_response):
:license: BSD, see LICENSE for more details.
"""
import cPickle as pickle
+from base64 import b64decode, b64encode
from hmac import new as hmac
from time import time
from werkzeug.urls import url_quote_plus, url_unquote_plus
-from werkzeug._internal import _date_to_unix
+from werkzeug._internal import _b, _date_to_unix
from werkzeug.contrib.sessions import ModificationTrackingDict
-from werkzeug.security import safe_str_cmp
+from werkzeug.security import safe_bytes_cmp, safe_str_cmp
+
+try:
+ bytes
+except:
+ bytes = str
from hashlib import sha1 as _default_hash
@@ -147,8 +153,8 @@ def __init__(self, data=None, secret_key=None, new=True):
ModificationTrackingDict.__init__(self, data or ())
# explicitly convert it into a bytestring because python 2.6
# no longer performs an implicit string conversion on hmac
- if secret_key is not None:
- secret_key = str(secret_key)
+ if isinstance(secret_key, unicode):
+ secret_key = secret_key.encode('ascii')
self.secret_key = secret_key
self.new = new
@@ -176,7 +182,9 @@ def quote(cls, value):
if cls.serialization_method is not None:
value = cls.serialization_method.dumps(value)
if cls.quote_base64:
- value = ''.join(value.encode('base64').splitlines()).strip()
+ value = _b('').join(b64encode(value).splitlines()).strip()
+ if not isinstance(value, str):
+ return value.decode('latin1')
return value
@classmethod
@@ -187,8 +195,9 @@ def unquote(cls, value):
:param value: the value to unquote.
"""
try:
+ value = value.encode('ascii')
if cls.quote_base64:
- value = value.decode('base64')
+ value = b64decode(value)
if cls.serialization_method is not None:
value = cls.serialization_method.loads(value)
return value
@@ -219,9 +228,9 @@ def serialize(self, expires=None):
url_quote_plus(key),
self.quote(value)
))
- mac.update('|' + result[-1])
+ mac.update(_b('|' + result[-1]))
return '%s?%s' % (
- mac.digest().encode('base64').strip(),
+ b64encode(mac.digest()).decode('ascii').strip(),
'&'.join(result)
)
@@ -233,8 +242,8 @@ def unserialize(cls, string, secret_key):
:param secret_key: the secret key used to serialize the cookie.
:return: a new :class:`SecureCookie`.
"""
- if isinstance(string, unicode):
- string = string.encode('utf-8', 'replace')
+ if isinstance(secret_key, unicode):
+ secret_key = secret_key.encode('ascii')
try:
base64_hash, data = string.split('?', 1)
except (ValueError, IndexError):
@@ -243,26 +252,24 @@ def unserialize(cls, string, secret_key):
items = {}
mac = hmac(secret_key, None, cls.hash_method)
for item in data.split('&'):
- mac.update('|' + item)
+ mac.update(_b('|' + item))
if not '=' in item:
items = None
break
key, value = item.split('=', 1)
# try to make the key a string
key = url_unquote_plus(key)
- try:
- key = str(key)
- except UnicodeError:
- pass
items[key] = value
# no parsing error and the mac looks okay, we can now
# sercurely unpickle our cookie.
try:
- client_hash = base64_hash.decode('base64')
+ client_hash = b64decode(base64_hash.encode('ascii'))
except Exception:
items = client_hash = None
- if items is not None and safe_str_cmp(client_hash, mac.digest()):
+ safe_cmp = (safe_bytes_cmp if isinstance(client_hash[0], int)
+ else safe_str_cmp)
+ if items is not None and safe_cmp(client_hash, mac.digest()):
try:
for key, value in items.iteritems():
items[key] = cls.unquote(value)
7 werkzeug/contrib/sessions.py
View
@@ -65,6 +65,7 @@ def application(environ, start_response):
from werkzeug.utils import dump_cookie, parse_cookie
from werkzeug.wsgi import ClosingIterator
from werkzeug.posixemulation import rename
+from werkzeug._internal import _b
_sha1_re = re.compile(r'^[a-f0-9]{40}$')
@@ -77,7 +78,7 @@ def _urandom():
def generate_key(salt=None):
- return sha1('%s%s%s' % (salt, time(), _urandom())).hexdigest()
+ return sha1(_b('%s%s%s' % (salt, time(), _urandom()))).hexdigest()
class ModificationTrackingDict(CallbackDict):
@@ -211,7 +212,7 @@ def __init__(self, path=None, filename_template='werkzeug_%s.sess',
if path is None:
path = tempfile.gettempdir()
self.path = path
- if isinstance(filename_template, unicode):
+ if sys.version_info < (3, ) and isinstance(filename_template, unicode):
filename_template = filename_template.encode(
sys.getfilesystemencoding() or 'utf-8')
assert not filename_template.endswith(_fs_transaction_suffix), \
@@ -224,7 +225,7 @@ def get_session_filename(self, sid):
# out of the box, this should be a strict ASCII subset but
# you might reconfigure the session object to have a more
# arbitrary string.
- if isinstance(sid, unicode):
+ if sys.version_info < (3, ) and isinstance(sid, unicode):
sid = sid.encode(sys.getfilesystemencoding() or 'utf-8')
return path.join(self.path, self.filename_template % sid)
5 werkzeug/contrib/wrappers.py
View
@@ -21,6 +21,7 @@ class Request(RequestBase, JSONRequestMixin):
:license: BSD, see LICENSE for more details.
"""
import codecs
+import sys
from werkzeug.exceptions import BadRequest
from werkzeug.utils import cached_property
from werkzeug.http import dump_options_header, parse_options_header
@@ -164,12 +165,16 @@ def path(self):
info in the WSGI environment but will not include a leading slash.
"""
path = (self.environ.get('PATH_INFO') or '').lstrip('/')
+ if sys.version_info >= (3, ):
+ return path
return _decode_unicode(path, self.charset, self.encoding_errors)
@cached_property
def script_root(self):
"""The root path of the script includling a trailing slash."""
path = (self.environ.get('SCRIPT_NAME') or '').rstrip('/') + '/'
+ if sys.version_info >= (3, ):
+ return path
return _decode_unicode(path, self.charset, self.encoding_errors)
47 werkzeug/datastructures.py
View
@@ -9,6 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
import re
+import sys
import codecs
import mimetypes
from itertools import repeat
@@ -493,7 +494,8 @@ def listvalues(self):
def iteritems(self, multi=False):
"""Like :meth:`items` but returns an iterator."""
- for key, values in dict.iteritems(self):
+ items = getattr(dict, 'items', None) or getattr(dict, 'iteritems')
+ for key, values in items(self):
if multi:
for value in values:
yield key, value
@@ -502,17 +504,20 @@ def iteritems(self, multi=False):
def iterlists(self):
"""Like :meth:`items` but returns an iterator."""
- for key, values in dict.iteritems(self):
+ items = getattr(dict, 'items', None) or getattr(dict, 'iteritems')
+ for key, values in items(self):
yield key, list(values)
def itervalues(self):
"""Like :meth:`values` but returns an iterator."""
- for values in dict.itervalues(self):
+ values = getattr(dict, 'values', None) or getattr(dict, 'itervalues')
+ for values in values(self):
yield values[0]
def iterlistvalues(self):
"""Like :meth:`listvalues` but returns an iterator."""
- return dict.itervalues(self)
+ values = getattr(dict, 'values', None) or getattr(dict, 'itervalues')
+ return values(self)
def copy(self):
"""Return a shallow copy of this object."""
@@ -596,27 +601,27 @@ class _omd_bucket(object):
a lot of extra memory and slows down access a lot, but makes it
possible to access elements in O(1) and iterate in O(n).
"""
- __slots__ = ('prev', 'key', 'value', 'next')
+ __slots__ = ('prev', 'key', 'value', 'next_')

Why did you do this?

Kyung-hown Chung Owner
puzzlet added a note

because 2to3 would incorrectly "fix" ptr.next into next(ptr). It seemed easier for me to change the name than 2to3's behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
def __init__(self, omd, key, value):
self.prev = omd._last_bucket
self.key = key
self.value = value
- self.next = None
+ self.next_ = None
if omd._first_bucket is None:
omd._first_bucket = self
if omd._last_bucket is not None:
- omd._last_bucket.next = self
+ omd._last_bucket.next_ = self
omd._last_bucket = self
def unlink(self, omd):
if self.prev:
- self.prev.next = self.next
- if self.next:
- self.next.prev = self.prev
+ self.prev.next_ = self.next_
+ if self.next_:
+ self.next_.prev = self.prev
if omd._first_bucket is self:
- omd._first_bucket = self.next
+ omd._first_bucket = self.next_
if omd._last_bucket is self:
omd._last_bucket = self.prev
@@ -705,14 +710,14 @@ def iteritems(self, multi=False):
if multi:
while ptr is not None:
yield ptr.key, ptr.value
- ptr = ptr.next
+ ptr = ptr.next_
else:
returned_keys = set()
while ptr is not None:
if ptr.key not in returned_keys:
returned_keys.add(ptr.key)
yield ptr.key, ptr.value
- ptr = ptr.next
+ ptr = ptr.next_
def iterlists(self):
returned_keys = set()
@@ -721,7 +726,7 @@ def iterlists(self):
if ptr.key not in returned_keys:
yield ptr.key, self.getlist(ptr.key)
returned_keys.add(ptr.key)
- ptr = ptr.next
+ ptr = ptr.next_
def iterlistvalues(self):
for key, values in self.iterlists():
@@ -1132,13 +1137,17 @@ def __setitem__(self, key, value):
self.set(key, value)
def to_list(self, charset='iso-8859-1'):
- """Convert the headers into a list and converts the unicode header
- items to the specified charset.
+ """Convert the headers into a list and converts the header items to str
+ using the specified charset.
:return: list
"""
- return [(k, isinstance(v, unicode) and v.encode(charset) or str(v))
- for k, v in self]
+ if sys.version_info >= (3, ):
+ return [(k, v.decode(charset) if isinstance(v, bytes) else str(v))
+ for k, v in self]
+ else:
+ return [(k, v.encode(charset) if isinstance(v, unicode) else str(v))
+ for k, v in self]
def copy(self):
return self.__class__(self._list)
@@ -1579,7 +1588,7 @@ def find(self, key):
def values(self):
"""Return a list of the values, not the qualities."""
- return list(self.itervalues())
+ return list(getattr(self, 'itervalues')())
Kyung-hown Chung Owner
puzzlet added a note

This is also a workaround to avoid 2to3 "fixing" self.itervalues() into self.values().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
def itervalues(self):
"""Iterate over all values."""
7 werkzeug/debug/console.py
View
@@ -15,6 +15,11 @@
from werkzeug.local import Local
from werkzeug.debug.repr import debug_repr, dump, helper
+try:
+ bytes
+except:
+ bytes = str
+

I think you should explicitly catch NameError here (and in similar cases)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
_local = Local()
@@ -50,7 +55,7 @@ def reset(self):
return val
def _write(self, x):
- if isinstance(x, str):
+ if isinstance(x, bytes):
x = x.decode('utf-8', 'replace')
self._buffer.append(x)
43 werkzeug/debug/repr.py
View
@@ -22,6 +22,11 @@
deque = None
from werkzeug.utils import escape
+try:
+ bytes
+except:
+ bytes = str
+
missing = object()
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
@@ -74,7 +79,9 @@ def __call__(self, topic=None):
return
import pydoc
pydoc.help(topic)
- rv = sys.stdout.reset().decode('utf-8', 'ignore')
+ rv = sys.stdout.reset()
+ if sys.version_info < (3, ):
+ rv = rv.decode('utf-8', 'ignore')
paragraphs = _paragraph_re.split(rv)
if len(paragraphs) > 1:
title = paragraphs[0]
@@ -135,8 +142,14 @@ def proxy(self, obj, recursive):
del _sequence_repr_maker
def regex_repr(self, obj):
- pattern = repr(obj.pattern).decode('string-escape', 'ignore')
- if pattern[:1] == 'u':
+ pattern = repr(obj.pattern)
+ if sys.version_info >= (3, ):
+ pattern = pattern.encode('ascii').decode('unicode-escape', 'ignore')
+ else:
+ pattern = pattern.decode('string-escape', 'ignore')
+ if pattern[:1] == 'b':
+ pattern = 'br' + pattern[1:]
+ elif pattern[:1] == 'u':
pattern = 'ur' + pattern[1:]
else:
pattern = 'r' + pattern
@@ -144,10 +157,16 @@ def regex_repr(self, obj):
def string_repr(self, obj, limit=70):
buf = ['<span class="string">']
- escaped = escape(obj)
+ escaped = (escape(obj) if isinstance(obj, unicode)
+ else escape(obj.decode('latin1')).encode('latin1'))
a = repr(escaped[:limit])
b = repr(escaped[limit:])
- if isinstance(obj, unicode):
+ if sys.version_info >= (3, ):
+ if isinstance(obj, bytes):
+ buf.append('b')
+ a = a[1:]
+ b = b[1:]
+ elif isinstance(obj, unicode):
buf.append('u')
a = a[1:]
b = b[1:]
@@ -156,7 +175,7 @@ def string_repr(self, obj, limit=70):
else:
buf.append(a)
buf.append('</span>')
- return _add_subclass_info(u''.join(buf), obj, (str, unicode))
+ return _add_subclass_info(u''.join(buf), obj, (bytes, unicode))
def dict_repr(self, d, recursive, limit=5):
if recursive:
@@ -178,15 +197,17 @@ def dict_repr(self, d, recursive, limit=5):
return _add_subclass_info(u''.join(buf), d, dict)
def object_repr(self, obj):
- return u'<span class="object">%s</span>' % \
- escape(repr(obj).decode('utf-8', 'replace'))
+ tmp = repr(obj)
+ if not isinstance(tmp, unicode):
+ tmp = tmp.decode('utf-8', 'replace')
+ return u'<span class="object">%s</span>' % escape(tmp)
def dispatch_repr(self, obj, recursive):
if obj is helper:
return u'<span class="help">%r</span>' % helper
if isinstance(obj, (int, long, float, complex)):
return u'<span class="number">%r</span>' % obj
- if isinstance(obj, basestring):
+ if isinstance(obj, (bytes, unicode)):
return self.string_repr(obj)
if isinstance(obj, RegexType):
return self.regex_repr(obj)
@@ -209,8 +230,10 @@ def fallback_repr(self):
info = ''.join(format_exception_only(*sys.exc_info()[:2]))
except Exception: # pragma: no cover
info = '?'
+ if not isinstance(info, unicode):
+ info = info.decode('utf-8', 'ignore')
return u'<span class="brokenrepr">&lt;broken repr (%s)&gt;' \
- u'</span>' % escape(info.decode('utf-8', 'ignore').strip())
+ u'</span>' % escape(info.strip())
def repr(self, obj):
recursive = False
11 werkzeug/debug/tbtools.py
View
@@ -253,14 +253,19 @@ def is_syntax_error(self):
def exception(self):
"""String representation of the exception."""
buf = traceback.format_exception_only(self.exc_type, self.exc_value)
- return ''.join(buf).strip().decode('utf-8', 'replace')
+ buf = ''.join(buf).strip()
+ if not isinstance(buf, unicode):
+ buf = buf.decode('utf-8', 'replace')
+ return buf
exception = property(exception)
def log(self, logfile=None):
"""Log the ASCII traceback into a file object."""
if logfile is None:
logfile = sys.stderr
- tb = self.plaintext.encode('utf-8', 'replace').rstrip() + '\n'
+ tb = self.plaintext.rstrip() + u'\n'
+ if isinstance(tb, unicode):
+ tb = tb.encode('utf-8', 'replace')
logfile.write(tb)
def paste(self, lodgeit_url):
@@ -439,7 +444,7 @@ def sourcelines(self):
if source is None:
try:
- f = file(self.filename)
+ f = open(self.filename)
except IOError:
return []
try:
2  werkzeug/exceptions.py
View
@@ -142,6 +142,8 @@ def __call__(self, environ, start_response):
return response(environ, start_response)
def __str__(self):
+ if sys.version_info > (3, ):
+ return self.__unicode__()
return unicode(self).encode('utf-8')
def __unicode__(self):
45 werkzeug/formparser.py
View
@@ -9,18 +9,27 @@
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
+import base64
import re
-from cStringIO import StringIO
+import sys
+try:
+ from io import BytesIO
+except ImportError:
+ from cStringIO import StringIO as BytesIO
from tempfile import TemporaryFile
from itertools import chain, repeat
from functools import update_wrapper
-from werkzeug._internal import _decode_unicode, _empty_stream
+from werkzeug._internal import _b, _decode_unicode, _empty_stream
from werkzeug.urls import url_decode_stream
from werkzeug.wsgi import LimitedStream, make_line_iter
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.datastructures import Headers, FileStorage, MultiDict
from werkzeug.http import parse_options_header
+try:
+ bytes
+except:
+ bytes = str
#: an iterator that yields empty strings
@@ -39,7 +48,7 @@ def default_stream_factory(total_content_length, filename, content_type,
"""The stream factory that is used per default."""
if total_content_length > 1024 * 500:
return TemporaryFile('wb+')
- return StringIO()
+ return BytesIO()
def parse_form_data(environ, stream_factory=None, charset='utf-8',
@@ -226,6 +235,8 @@ def _line_parse(line):
"""Removes line ending characters and returns a tuple (`stripped_line`,
`is_terminated`).
"""
+ if sys.version_info >= (3, ) and isinstance(line, bytes):
+ line = line.decode('latin1')
if line[-2:] == '\r\n':
return line[:-2], True
elif line[-1:] in '\r\n':
@@ -324,7 +335,8 @@ def get_part_charset(self, headers):
return self.charset
def start_file_streaming(self, filename, headers, total_content_length):
- filename = _decode_unicode(filename, self.charset, self.errors)
+ if sys.version_info < (3, ):
+ filename = _decode_unicode(filename, self.charset, self.errors)
filename = self._fix_ie_filename(filename)
content_type = headers.get('content-type')
try:
@@ -351,8 +363,8 @@ def validate_boundary(self, boundary):
self.fail('Boundary longer than buffer size')
def parse(self, file, boundary, content_length):
- next_part = '--' + boundary
- last_part = next_part + '--'
+ next_part = _b('--' + boundary)
+ last_part = next_part + _b('--')
form = []
files = []
@@ -395,19 +407,22 @@ def parse(self, file, boundary, content_length):
filename, headers, content_length)
_write = container.write
- buf = ''
+ buf = _b('')
for line in iterator:
if not line:
self.fail('unexpected end of stream')
- if line[:2] == '--':
+ if line[:2] == _b('--'):
terminator = line.rstrip()
if terminator in (next_part, last_part):
break
if transfer_encoding is not None:
try:
- line = line.decode(transfer_encoding)
+ if transfer_encoding == 'base64':
+ line = base64.b64decode(line)
+ else:
+ line = line.decode(transfer_encoding)
except Exception:
self.fail('could not decode transfer encoded chunk')
@@ -415,7 +430,7 @@ def parse(self, file, boundary, content_length):
# this is usually a newline delimiter.
if buf:
_write(buf)
- buf = ''
+ buf = _b('')
# If the line ends with windows CRLF we write everything except
# the last two bytes. In all other cases however we write
@@ -426,11 +441,11 @@ def parse(self, file, boundary, content_length):
# truncate the stream. However we do have to make sure that
# if something else than a newline is in there we write it
# out.
- if line[-2:] == '\r\n':
- buf = '\r\n'
+ if line[-2:] == _b('\r\n'):
+ buf = _b('\r\n')
cutoff = -2
else:
- buf = line[-1]
+ buf = line[-1:]
cutoff = -1
_write(line[:cutoff])
@@ -447,7 +462,7 @@ def parse(self, file, boundary, content_length):
# if we have a leftover in the buffer that is not a newline
# character we have to flush it, otherwise we will chop of
# certain values.
- if buf not in ('', '\r', '\n', '\r\n'):
+ if buf not in (_b(''), _b('\r'), _b('\n'), _b('\r\n')):
_write(buf)
if is_file:
@@ -455,7 +470,7 @@ def parse(self, file, boundary, content_length):
files.append((name, FileStorage(container, filename, name,
headers=headers)))
else:
- form.append((name, _decode_unicode(''.join(container),
+ form.append((name, _decode_unicode(_b('').join(container),
part_charset, self.errors)))
return self.cls(form), self.cls(files)
51 werkzeug/http.py
View
@@ -17,22 +17,35 @@
:license: BSD, see LICENSE for more details.
"""
import re
+import sys
+from base64 import b64decode
from time import time
try:
from email.utils import parsedate_tz
except ImportError: # pragma: no cover
from email.Utils import parsedate_tz
-from urllib2 import parse_http_list as _parse_list_header
+try:
+ from urllib.request import parse_http_list as _parse_list_header
+except ImportError:
+ from urllib2 import parse_http_list as _parse_list_header
from datetime import datetime, timedelta
try:
from hashlib import md5
except ImportError: # pragma: no cover
from md5 import new as md5
+try:
+ bytes
+except:
+ bytes = str
+try:
+ next
+except:
+ next = lambda _: _.next()
#: HTTP_STATUS_CODES is "exported" from this module.
#: XXX: move to werkzeug.consts or something
-from werkzeug._internal import HTTP_STATUS_CODES, _dump_date, \
+from werkzeug._internal import HTTP_STATUS_CODES, _b, _dump_date, \
_ExtendedCookie, _ExtendedMorsel, _decode_unicode
@@ -240,7 +253,7 @@ def _tokenize(string):
return '', {}
parts = _tokenize(';' + value)
- name = parts.next()[0]
+ name = next(parts)[0]
extra = dict(parts)
return name, extra
@@ -349,12 +362,18 @@ def parse_authorization_header(value):
except ValueError:
return
if auth_type == 'basic':
+ auth_info = auth_info.encode('ascii')
try:
- username, password = auth_info.decode('base64').split(':', 1)
+ username, password = b64decode(auth_info).split(_b(':'), 1)
except Exception, e:
return
- return Authorization('basic', {'username': username,
- 'password': password})
+ if sys.version_info >= (3, ):
+ return Authorization('basic', {
+ 'username': username.decode('latin1'),
+ 'password': password.decode('latin1')})
+ else:
+ return Authorization('basic', {'username': username,
+ 'password': password})
elif auth_type == 'digest':
auth_map = parse_dict_header(auth_info)
for key in 'username', 'realm', 'nonce', 'uri', 'response':
@@ -558,6 +577,8 @@ def parse_etags(value):
def generate_etag(data):
"""Generate an etag for some data."""
+ if isinstance(data, unicode):
+ data = data.encode('utf-8')
return md5(data).hexdigest()
@@ -746,8 +767,9 @@ def parse_cookie(header, charset='utf-8', errors='replace',
# `None` items which we have to skip here.
for key, value in cookie.iteritems():
if value.value is not None:
- result[key] = _decode_unicode(unquote_header_value(value.value),
- charset, errors)
+ result[key] = _decode_unicode(
+ unquote_header_value(value.value).encode('ascii'),
+ charset, errors)
return cls(result)
@@ -780,11 +802,16 @@ def dump_cookie(key, value='', max_age=None, expires=None, path='/',
but expires not.
"""
try:
- key = str(key)
- except UnicodeError:
+ key.encode('ascii')
+ except UnicodeEncodeError:
raise TypeError('invalid key %r' % key)
- if isinstance(value, unicode):
- value = value.encode(charset)
+ try:
+ if isinstance(value, bytes):
+ value = value.decode('ascii')
+ else:
+ value.encode('ascii')
+ except UnicodeError:
+ raise TypeError('invalid value %r' % value)
value = quote_header_value(value)
morsel = _ExtendedMorsel(key, value)
if isinstance(max_age, timedelta):
66 werkzeug/routing.py
View
@@ -97,6 +97,7 @@
"""
import re
import posixpath
+import sys
from pprint import pformat
from urlparse import urljoin
@@ -816,9 +817,12 @@ def __repr__(self):
tmp.append('<%s>' % data)
else:
tmp.append(data)
+ tmp = u''.join(tmp)
+ if sys.version_info < (3, ):
+ tmp = tmp.encode(charset)
return '<%s %r%s -> %s>' % (
self.__class__.__name__,
- (u''.join(tmp).encode(charset)).lstrip('|'),
+ tmp.lstrip('|'),
self.methods is not None and ' (%s)' % \
', '.join(self.methods) or '',
self.endpoint
@@ -1123,6 +1127,7 @@ def bind(self, server_name, script_name=None, subdomain=None,
script_name = '/'
if isinstance(server_name, unicode):
server_name = server_name.encode('idna')
+ server_name = server_name.decode('ascii')
return MapAdapter(self, server_name, script_name, subdomain,
url_scheme, path_info, default_method, query_args)
@@ -1229,7 +1234,7 @@ def __init__(self, map, server_name, script_name, subdomain,
self.default_method = default_method
self.query_args = query_args
- def dispatch(self, view_func, path_info=None, method=None,
+ def dispatch(self, view_func, path=None, method=None, path_info=None,
catch_http_exceptions=False):
"""Does the complete dispatching process. `view_func` is called with
the endpoint and a dict with the values for the view. It should
@@ -1266,16 +1271,18 @@ def application(environ, start_response):
first argument and the value dict as second. Has
to dispatch to the actual view function with this
information. (see above)
- :param path_info: the path info to use for matching. Overrides the
- path info specified on binding.
+ :param path: the path info to use for matching. Overrides the
+ path info specified on binding.
:param method: the HTTP method used for matching. Overrides the
method specified on binding.
+ :param path_info: same as `path`, but for directly passing
+ `environ['PATH_INFO']`.
:param catch_http_exceptions: set to `True` to catch any of the
werkzeug :class:`HTTPException`\s.
"""
try:
try:
- endpoint, args = self.match(path_info, method)
+ endpoint, args = self.match(path, method, path_info=path_info)
except RequestRedirect, e:
return e
return view_func(endpoint, args)
@@ -1284,8 +1291,8 @@ def application(environ, start_response):
return e
raise
- def match(self, path_info=None, method=None, return_rule=False,
- query_args=None):
+ def match(self, path=None, method=None, return_rule=False,
+ path_info=None, query_args=None):
"""The usage is simple: you just pass the match method the current
path info as well as the method (which defaults to `GET`). The
following things can then happen:
@@ -1342,12 +1349,16 @@ def match(self, path_info=None, method=None, return_rule=False,
...
NotFound: 404 Not Found
- :param path_info: the path info to use for matching. Overrides the
- path info specified on binding.
+ :param path: the path info to use for matching. Overrides the
+ path info specified on binding.
:param method: the HTTP method used for matching. Overrides the
method specified on binding.
:param return_rule: return the rule that matched instead of just the
endpoint (defaults to `False`).
+ :param path_info: same as `path`, but use this when you pass
+ `environ['PATH_INFO']` (which is a unicode string
+ encoded in ISO 8859-1 from the bytestring, as
+ specified in PEP 3333).
:param query_args: optional query arguments that are used for
automatic redirects as string or dictionary. It's
currently not possible to use the query arguments
@@ -1363,28 +1374,31 @@ def match(self, path_info=None, method=None, return_rule=False,
`query_args` can now also be a string.
"""
self.map.update()
+ assert not (path and path_info), \
+ "You can't give both path and path_info"
if path_info is None:
path_info = self.path_info
- if not isinstance(path_info, unicode):
- path_info = path_info.decode(self.map.charset,
- self.map.encoding_errors)
+ if path is None:
+ path = path_info.encode('latin1')
+ if not isinstance(path, unicode):
+ path = path.decode(self.map.charset, self.map.encoding_errors)
if query_args is None:
query_args = self.query_args
method = (method or self.default_method).upper()
- path = u'%s|/%s' % (self.map.host_matching and self.server_name or
- self.subdomain, path_info.lstrip('/'))
+ path_ = u'%s|/%s' % (self.map.host_matching and self.server_name or
+ self.subdomain, path.lstrip('/'))
have_match_for = set()
for rule in self.map._rules:
try:
- rv = rule.match(path)
+ rv = rule.match(path_)
except RequestSlash:
raise RequestRedirect(self.make_redirect_url(
- path_info + '/', query_args))
+ path + '/', query_args))
except RequestAliasRedirect, e:
raise RequestRedirect(self.make_alias_redirect_url(
- path, rule.endpoint, e.matched_values, method, query_args))
+ path_, rule.endpoint, e.matched_values, method, query_args))
if rv is None:
continue
if rule.methods is not None and method not in rule.methods:
@@ -1422,30 +1436,32 @@ def _handle_match(match):
raise MethodNotAllowed(valid_methods=list(have_match_for))
raise NotFound()
- def test(self, path_info=None, method=None):
+ def test(self, path=None, method=None, path_info=None):
"""Test if a rule would match. Works like `match` but returns `True`
if the URL matches, or `False` if it does not exist.
- :param path_info: the path info to use for matching. Overrides the
- path info specified on binding.
+ :param path: the path info to use for matching. Overrides the
+ path info specified on binding.
:param method: the HTTP method used for matching. Overrides the
method specified on binding.
+ :param path_info: same as `path` but for directly passing
+ `environ['PATH_INFO']`/
"""
try:
- self.match(path_info, method)
+ self.match(path, method, path_info=path_info)
except RequestRedirect:
pass
except HTTPException:
return False
return True
- def allowed_methods(self, path_info=None):
+ def allowed_methods(self, path=None, path_info=None):
"""Returns the valid methods that match for a given path.
.. versionadded:: 0.7
"""
try:
- self.match(path_info, method='--')
+ self.match(path, method='--', path_info=path_info)
except MethodNotAllowed, e:
return e.valid_methods
except HTTPException, e:
@@ -1491,7 +1507,7 @@ def encode_query_args(self, query_args):
query_args = url_encode(query_args, self.map.charset)
return query_args
- def make_redirect_url(self, path_info, query_args=None, domain_part=None):
+ def make_redirect_url(self, path, query_args=None, domain_part=None):
"""Creates a redirect URL.
:internal:
@@ -1503,7 +1519,7 @@ def make_redirect_url(self, path_info, query_args=None, domain_part=None):
self.url_scheme,
self.get_host(domain_part),
posixpath.join(self.script_name[:-1].lstrip('/'),
- url_quote(path_info.lstrip('/'), self.map.charset,
+ url_quote(path.lstrip('/'), self.map.charset,
safe='/:|+')),
suffix
))
10 werkzeug/security.py
View
@@ -53,6 +53,16 @@ def safe_str_cmp(a, b):
return rv == 0
+def safe_bytes_cmp(a, b):
+ """Same as safe_str_cmp, but for bytes"""
+ if len(a) != len(b):
+ return False
+ rv = 0
+ for x, y in izip(a, b):
+ rv |= x ^ y
+ return rv == 0
+
+
def gen_salt(length):
"""Generate a random string of SALT_CHARS with specified ``length``."""
if length <= 0:
15 werkzeug/serving.py
View
@@ -49,9 +49,14 @@
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
import werkzeug
-from werkzeug._internal import _log
+from werkzeug._internal import _b, _log
from werkzeug.exceptions import InternalServerError
+try:
+ bytes
+except:
+ bytes = str
+
class WSGIRequestHandler(BaseHTTPRequestHandler, object):
"""A request handler that implements WSGI dispatching."""
@@ -94,6 +99,8 @@ def shutdown_server():
'SERVER_PORT': str(self.server.server_address[1]),
'SERVER_PROTOCOL': self.request_version
}
+ if sys.version_info >= (3, ):
+ environ['PATH_INFO'] = unquote(path_info, encoding='latin1')
for key, value in self.headers.items():
key = 'HTTP_' + key.upper().replace('-', '_')
@@ -128,7 +135,7 @@ def write(data):
self.send_header('Date', self.date_time_string())
self.end_headers()
- assert type(data) is str, 'applications must write bytes'
+ assert type(data) is bytes, 'applications must write bytes'
self.wfile.write(data)
self.wfile.flush()
@@ -224,8 +231,8 @@ def send_response(self, code, message=None):
if message is None:
message = code in self.responses and self.responses[code][0] or ''
if self.request_version != 'HTTP/0.9':
- self.wfile.write("%s %d %s\r\n" %
- (self.protocol_version, code, message))
+ self.wfile.write(_b("%s %d %s\r\n" %
+ (self.protocol_version, code, message)))
def version_string(self):
return BaseHTTPRequestHandler.version_string(self).strip()
53 werkzeug/test.py
View
@@ -15,11 +15,14 @@
from random import random
from itertools import chain
from tempfile import TemporaryFile
-from cStringIO import StringIO
+if sys.version_info > (3, ):
+ from io import BytesIO
+else:
+ from cStringIO import StringIO as BytesIO
from cookielib import CookieJar
from urllib2 import Request as U2Request
-from werkzeug._internal import _empty_stream, _get_environ
+from werkzeug._internal import _b, _empty_stream, _get_environ
from werkzeug.wrappers import BaseRequest
from werkzeug.urls import url_encode, url_fix, iri_to_uri, _unquote
from werkzeug.wsgi import get_host, get_current_url, ClosingIterator
@@ -27,6 +30,11 @@
from werkzeug.datastructures import FileMultiDict, MultiDict, \
CombinedMultiDict, Headers, FileStorage
+try:
+ bytes
+except:
+ bytes = str
+
def stream_encode_multipart(values, use_tempfile=True, threshold=1024 * 500,
boundary=None, charset='utf-8'):
@@ -36,7 +44,7 @@ def stream_encode_multipart(values, use_tempfile=True, threshold=1024 * 500,
"""
if boundary is None:
boundary = '---------------WerkzeugFormPart_%s%s' % (time(), random())
- _closure = [StringIO(), 0, False]
+ _closure = [BytesIO(), 0, False]
if use_tempfile:
def write(string):
@@ -62,8 +70,8 @@ def write(string):
for key, values in values.iterlists():
for value in values:
- write('--%s\r\nContent-Disposition: form-data; name="%s"' %
- (boundary, key))
+ write(_b('--%s\r\nContent-Disposition: form-data; name="%s"' %
+ (boundary, key)))
reader = getattr(value, 'read', None)
if reader is not None:
filename = getattr(value, 'filename',
@@ -74,21 +82,23 @@ def write(string):
mimetypes.guess_type(filename)[0] or \
'application/octet-stream'
if filename is not None:
- write('; filename="%s"\r\n' % filename)
+ write(_b('; filename="%s"\r\n' % filename))
else:
write('\r\n')
- write('Content-Type: %s\r\n\r\n' % content_type)
+ write(_b('Content-Type: %s\r\n\r\n' % content_type))
while 1:
chunk = reader(16384)
if not chunk:
break
write(chunk)
else:
+ if not isinstance(value, bytes):
+ value = str(value)
if isinstance(value, unicode):
value = value.encode(charset)
- write('\r\n\r\n' + str(value))
- write('\r\n')
- write('--%s--\r\n' % boundary)
+ write(_b('\r\n\r\n') + value)
+ write(_b('\r\n'))
+ write(_b('--%s--\r\n' % boundary))
length = int(_closure[0].tell())
_closure[0].seek(0)
@@ -123,10 +133,17 @@ def getheaders(self, name):
headers = []
name = name.lower()
for k, v in self.headers:
- if k.lower() == name:
+ if k.lower() == name.lower():
headers.append(v)
return headers
+ def get_all(self, name, default=None):
+ rv = []
+ for k, v in self.headers:
+ if k.lower() == name.lower():
+ rv.append(v)
+ return rv or default or []
+
class _TestCookieResponse(object):
"""Something that looks like a httplib.HTTPResponse, but is actually just an
@@ -300,9 +317,11 @@ def __init__(self, path='/', base_url=None, query_string=None,
if data:
if input_stream is not None:
- raise TypeError('can\'t provide input stream and data')
- if isinstance(data, basestring):
- self.input_stream = StringIO(data)
+ raise TypeError('can\'t provide both input stream and data')
+ if isinstance(data, unicode):
+ data = data.encode(self.charset)
+ if isinstance(data, bytes):
+ self.input_stream = BytesIO(data)
if self.content_length is None:
self.content_length = len(data)
else:
@@ -515,8 +534,9 @@ def get_environ(self):
content_type += '; boundary="%s"' % boundary
elif content_type == 'application/x-www-form-urlencoded':
values = url_encode(self.form, charset=self.charset)
+ values = values.encode('ascii')
content_length = len(values)
- input_stream = StringIO(values)
+ input_stream = BytesIO(values)
else:
input_stream = _empty_stream
@@ -525,6 +545,9 @@ def get_environ(self):
result.update(self.environ_base)
def _path_encode(x):
+ if sys.version_info >= (3, ):
+ # NOTE: PEP 3333 for Python 3
+ return _unquote(x).decode('latin1')
if isinstance(x, unicode):
x = x.encode(self.charset)
return _unquote(x)
10 werkzeug/testapp.py
View
@@ -9,15 +9,18 @@
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
+from base64 import b64decode
import os
import sys
import werkzeug
from textwrap import wrap
from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
from werkzeug.utils import escape
+from werkzeug._internal import _b
-logo = Response('''R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP/////////
+logo = Response(b64decode(_b(
+'''R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP/////////
//////////////////////yH5BAEKAAgALAAAAACgAKAAAAT+EMlJq704680R+F0ojmRpnuj0rWnrv
nB8rbRs33gu0bzu/0AObxgsGn3D5HHJbCUFyqZ0ukkSDlAidctNFg7gbI9LZlrBaHGtzAae0eloe25
7w9EDOX2fst/xenyCIn5/gFqDiVVDV4aGeYiKkhSFjnCQY5OTlZaXgZp8nJ2ekaB0SQOjqphrpnOiq
@@ -50,7 +53,7 @@
Oco1srWtkaVrMUzIErrKri85keKqRQYX9VX0/eAUK1hrSu6HMEX3Qh2sCh0q0D2CtnUqS4hj62sE/z
aDs2Sg7MBS6xnQeooc2R2tC9YrKpEi9pLXfYXp20tDCpSP8rKlrD4axprb9u1Df5hSbz9QU0cRpfgn
kiIzwKucd0wsEHlLpe5yHXuc6FrNelOl7pY2+11kTWx7VpRu97dXA3DO1vbkhcb4zyvERYajQgAADs
-='''.decode('base64'), mimetype='image/png')
+=''')), mimetype='image/png')
TEMPLATE = u'''\
@@ -156,8 +159,7 @@ def render_testapp(req):
eggs = ()
else:
eggs = list(pkg_resources.working_set)
- eggs.sort(lambda a, b: cmp(a.project_name.lower(),
- b.project_name.lower()))
+ eggs.sort(key=lambda a: a.project_name.lower())
python_eggs = []
for egg in eggs:
try:
1  werkzeug/testsuite/__init__.py
View
@@ -143,4 +143,5 @@ def main():
try:
unittest.main(testLoader=BetterLoader(), defaultTest='suite')
except Exception, e:
+ raise
print 'Error: %s' % e
3  werkzeug/testsuite/compat.py
View
@@ -14,6 +14,7 @@
from werkzeug.wrappers import Response
from werkzeug.test import create_environ
+from werkzeug._internal import _b
class CompatTestCase(WerkzeugTestCase):
@@ -47,7 +48,7 @@ def fix_headers(self, environ):
myresp = MyResponse('Foo')
resp = Response.from_app(myresp, create_environ(method='GET'))
assert resp.headers['x-foo'] == 'meh'
- assert resp.data == 'Foo'
+ assert resp.data == _b('Foo')
warnings.resetwarnings()
5 werkzeug/testsuite/contrib/cache.py
View
@@ -16,6 +16,7 @@
from werkzeug.testsuite import WerkzeugTestCase
from werkzeug.contrib import cache
+from werkzeug._internal import _b
try:
import redis
@@ -95,8 +96,8 @@ def teardown(self):
def test_compat(self):
c = self.make_cache()
- c._client.set(c.key_prefix + 'foo', 'Awesome')
- self.assert_equal(c.get('foo'), 'Awesome')
+ c._client.set(c.key_prefix + 'foo', _b('Awesome'))
+ self.assert_equal(c.get('foo'), _b('Awesome'))
c._client.set(c.key_prefix + 'foo', '42')
self.assert_equal(c.get('foo'), 42)
19 werkzeug/testsuite/contrib/fixers.py
View
@@ -18,6 +18,7 @@
from werkzeug.wrappers import Request, Response
from werkzeug.contrib import fixers
from werkzeug.utils import redirect
+from werkzeug._internal import _b
@Request.application
@@ -37,7 +38,7 @@ def test_lighttpd_cgi_root_fix(self):
PATH_INFO='/bar',
SERVER_SOFTWARE='lighttpd/1.4.27'
))
- assert response.data == 'PATH_INFO: /foo/bar\nSCRIPT_NAME: '
+ assert response.data == _b('PATH_INFO: /foo/bar\nSCRIPT_NAME: ')
def test_path_info_from_request_uri_fix(self):
app = fixers.PathInfoFromRequestUriFix(path_check_app)
@@ -45,7 +46,7 @@ def test_path_info_from_request_uri_fix(self):
env = dict(create_environ(), SCRIPT_NAME='/test', PATH_INFO='/?????')
env[key] = '/test/foo%25bar?drop=this'
response = Response.from_app(app, env)
- assert response.data == 'PATH_INFO: /foo%bar\nSCRIPT_NAME: /test'
+ assert response.data == _b('PATH_INFO: /foo%bar\nSCRIPT_NAME: /test')
def test_proxy_fix(self):
@fixers.ProxyFix
@@ -66,7 +67,7 @@ def app(request):
response = Response.from_app(app, environ)
- assert response.data == '1.2.3.4|example.com'
+ assert response.data == _b('1.2.3.4|example.com')
# And we must check that if it is a redirection it is
# correctly done:
@@ -88,7 +89,7 @@ def app(request):
)
response = Response.from_app(app, environ)
- self.assert_equal(response.data, '127.0.0.1')
+ self.assert_equal(response.data, _b('127.0.0.1'))
def test_header_rewriter_fix(self):
@Request.application
@@ -120,7 +121,7 @@ def application(request):
])
# IE gets no vary
- assert response.data == 'binary data here'
+ assert response.data == _b('binary data here')
assert 'vary' not in response.headers
assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
assert response.headers['content-type'] == 'application/vnd.ms-excel'
@@ -128,7 +129,7 @@ def application(request):
# other browsers do
c = Client(application, Response)
response = c.get('/')
- assert response.data == 'binary data here'
+ assert response.data == _b('binary data here')
assert 'vary' in response.headers
cc = ResponseCacheControl()
@@ -150,7 +151,7 @@ def application(request):
response = c.get('/', headers=[
('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
])
- assert response.data == 'binary data here'
+ assert response.data == _b('binary data here')
assert 'pragma' not in response.headers
assert 'cache-control' not in response.headers
assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
@@ -161,14 +162,14 @@ def application(request):
response = c.get('/', headers=[
('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
])
- assert response.data == 'binary data here'
+ assert response.data == _b('binary data here')
assert response.headers['pragma'] == 'x-foo'
assert response.headers['cache-control'] == 'proxy-revalidate'
assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
# regular browsers get everything
response = c.get('/')
- assert response.data == 'binary data here'
+ assert response.data == _b('binary data here')
assert response.headers['pragma'] == 'no-cache, x-foo'
cc = parse_cache_control_header(response.headers['cache-control'],
cls=ResponseCacheControl)
3  werkzeug/testsuite/contrib/securecookie.py
View
@@ -15,6 +15,7 @@
from werkzeug.utils import parse_cookie
from werkzeug.wrappers import Request, Response
from werkzeug.contrib.securecookie import SecureCookie
+from werkzeug._internal import _b
class SecureCookieTestCase(WerkzeugTestCase):
@@ -47,7 +48,7 @@ def test_wrapper_support(self):
c = SecureCookie.load_cookie(req, secret_key='foo')
assert c.new
c['foo'] = 42
- assert c.secret_key == 'foo'
+ assert c.secret_key == _b('foo')
c.save_cookie(resp)
req = Request.from_values(headers={
3  werkzeug/testsuite/contrib/wrappers.py
View
@@ -18,6 +18,7 @@
from werkzeug.contrib import wrappers
from werkzeug import routing
from werkzeug.wrappers import Request, Response
+from werkzeug._internal import _b
class WrappersTestCase(WerkzeugTestCase):
@@ -80,7 +81,7 @@ class MyResponse(wrappers.DynamicCharsetResponseMixin, Response):
resp.mimetype_params['charset'] = 'iso-8859-15'
assert resp.charset == 'iso-8859-15'
resp.data = u'Hällo Wörld'
- assert ''.join(resp.iter_encoded()) == \
+ assert _b('').join(resp.iter_encoded()) == \
u'Hällo Wörld'.encode('iso-8859-15')
del resp.headers['content-type']
try:
45 werkzeug/testsuite/datastructures.py
View
@@ -26,6 +26,7 @@
from werkzeug import datastructures
from werkzeug.exceptions import BadRequestKeyError
+from werkzeug._internal import _b
class MutableMultiDictBaseTestCase(WerkzeugTestCase):
@@ -43,7 +44,7 @@ def test_pickle(self):
self.assert_equal(type(ud), type(d))
self.assert_equal(ud, d)
self.assert_equal(pickle.loads(
- s.replace('werkzeug.datastructures', 'werkzeug')), d)
+ s.replace(_b('werkzeug.datastructures'), _b('werkzeug'))), d)
ud['newkey'] = 'bla'
self.assert_not_equal(ud, d)
@@ -213,8 +214,8 @@ def test_follows_dict_interface(self):
self.assert_equal(d['bar'], 2)
self.assert_equal(d['baz'], 3)
self.assert_equal(sorted(d.keys()), ['bar', 'baz', 'foo'])
- self.assert_('foo' in d)
- self.assert_('foox' not in d)
+ self.assertTrue('foo' in d)
+ self.assertTrue('foox' not in d)
self.assert_equal(len(d), 3)
def test_copies_are_mutable(self):
@@ -225,26 +226,26 @@ def test_copies_are_mutable(self):
mutable = immutable.copy()
mutable.pop('a')
- self.assert_('a' in immutable)
- self.assert_(mutable is not immutable)
- self.assert_(copy(immutable) is immutable)
+ self.assertTrue('a' in immutable)
+ self.assertTrue(mutable is not immutable)
+ self.assertTrue(copy(immutable) is immutable)
def test_dict_is_hashable(self):
cls = self.storage_class
immutable = cls({'a': 1, 'b': 2})
immutable2 = cls({'a': 2, 'b': 2})
x = set([immutable])
- self.assert_(immutable in x)
- self.assert_(immutable2 not in x)
+ self.assertTrue(immutable in x)
+ self.assertTrue(immutable2 not in x)
x.discard(immutable)
- self.assert_(immutable not in x)
- self.assert_(immutable2 not in x)
+ self.assertTrue(immutable not in x)
+ self.assertTrue(immutable2 not in x)
x.add(immutable2)
- self.assert_(immutable not in x)
- self.assert_(immutable2 in x)
+ self.assertTrue(immutable not in x)
+ self.assertTrue(immutable2 in x)
x.add(immutable)
- self.assert_(immutable in x)
- self.assert_(immutable2 in x)
+ self.assertTrue(immutable in x)
+ self.assertTrue(immutable2 in x)
class ImmutableTypeConversionDictTestCase(ImmutableDictBaseTestCase):
@@ -259,17 +260,17 @@ def test_multidict_is_hashable(self):
immutable = cls({'a': [1, 2], 'b': 2})
immutable2 = cls({'a': [1], 'b': 2})
x = set([immutable])
- self.assert_(immutable in x)
- self.assert_(immutable2 not in x)
+ self.assertTrue(immutable in x)
+ self.assertTrue(immutable2 not in x)
x.discard(immutable)
- self.assert_(immutable not in x)
- self.assert_(immutable2 not in x)
+ self.assertTrue(immutable not in x)
+ self.assertTrue(immutable2 not in x)
x.add(immutable2)
- self.assert_(immutable not in x)
- self.assert_(immutable2 in x)
+ self.assertTrue(immutable not in x)
+ self.assertTrue(immutable2 in x)
x.add(immutable)
- self.assert_(immutable in x)
- self.assert_(immutable2 in x)
+ self.assertTrue(immutable in x)
+ self.assertTrue(immutable2 in x)
class ImmutableDictTestCase(ImmutableDictBaseTestCase):
28 werkzeug/testsuite/debug.py
View
@@ -17,6 +17,7 @@
from werkzeug.debug.repr import debug_repr, DebugReprGenerator, \
dump, helper
from werkzeug.debug.console import HTMLStringO
+from werkzeug._internal import _b
class DebugReprTestCase(WerkzeugTestCase):
@@ -53,9 +54,14 @@ def test_mapping_repr(self):
u'</span></span></span>}'
assert debug_repr(dict(zip(range(10), [None] * 10))) == \
u'{<span class="pair"><span class="key"><span class="number">0</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">1</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">2</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">3</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="extended"><span class="pair"><span class="key"><span class="number">4</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">5</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">6</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">7</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">8</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">9</span></span>: <span class="value"><span class="object">None</span></span></span></span>}'
- assert debug_repr((1, 'zwei', u'drei')) ==\
- u'(<span class="number">1</span>, <span class="string">\'' \
- u'zwei\'</span>, <span class="string">u\'drei\'</span>)'
+ if sys.version_info >= (3, ):
+ assert debug_repr((1, 'zwei', _b('drei'))) ==\
+ '(<span class="number">1</span>, <span class="string">\'' \
+ 'zwei\'</span>, <span class="string">b\'drei\'</span>)'
+ else:
+ assert debug_repr((1, 'zwei', u'drei')) ==\
+ u'(<span class="number">1</span>, <span class="string">\'' \
+ u'zwei\'</span>, <span class="string">u\'drei\'</span>)'
def test_custom_repr(self):
class Foo(object):
@@ -73,8 +79,12 @@ class MyList(list):
def test_regex_repr(self):
assert debug_repr(re.compile(r'foo\d')) == \
u're.compile(<span class="string regex">r\'foo\\d\'</span>)'
- assert debug_repr(re.compile(ur'foo\d')) == \
- u're.compile(<span class="string regex">ur\'foo\\d\'</span>)'
+ if sys.version_info >= (3, ):
+ assert debug_repr(re.compile(_b(r'foo\d'))) == \
+ 're.compile(<span class="string regex">br\'foo\\d\'</span>)'
+ else:
+ assert debug_repr(re.compile(ur'foo\d')) == \
+ u're.compile(<span class="string regex">ur\'foo\\d\'</span>)'
def test_set_repr(self):
assert debug_repr(frozenset('x')) == \
@@ -92,9 +102,15 @@ class Foo(object):
def __repr__(self):
1/0
+ if sys.version_info >= (3, 2):
+ message = u'division by zero'
+ elif sys.version_info >= (3, 1):
+ message = u'int division or modulo by zero'
+ else:
+ message = u'integer division or modulo by zero'
assert debug_repr(Foo()) == \
u'<span class="brokenrepr">&lt;broken repr (ZeroDivisionError: ' \
- u'integer division or modulo by zero)&gt;</span>'
+ u'%s)&gt;</span>' % message
class DebugHelpersTestCase(WerkzeugTestCase):
7 werkzeug/testsuite/exceptions.py
View
@@ -18,6 +18,7 @@
from werkzeug import exceptions
from werkzeug.wrappers import Response
+from werkzeug._internal import _b
class ExceptionsTestCase(WerkzeugTestCase):
@@ -30,8 +31,8 @@ def test_proxy_exception(self):
resp = e.get_response({})
else:
self.fail('exception not raised')
- self.assert_(resp is orig_resp)
- self.assert_equal(resp.data, 'Hello World')
+ self.assertTrue(resp is orig_resp)
+ self.assert_equal(resp.data, _b('Hello World'))
def test_aborter(self):
abort = exceptions.abort
@@ -75,7 +76,7 @@ def test_special_exceptions(self):
exc = exceptions.MethodNotAllowed(['GET', 'HEAD', 'POST'])
h = dict(exc.get_headers({}))
self.assert_equal(h['Allow'], 'GET, HEAD, POST')
- self.assert_('The method DELETE is not allowed' in exc.get_description({
+ self.assertTrue('The method DELETE is not allowed' in exc.get_description({
'REQUEST_METHOD': 'DELETE'
}))
141 werkzeug/testsuite/formparser.py
View
@@ -12,7 +12,11 @@
from __future__ import with_statement
import unittest
-from StringIO import StringIO
+import sys
+try:
+ from io import BytesIO
+except ImportError:
+ from StringIO import StringIO as BytesIO
from os.path import join, dirname
from werkzeug.testsuite import WerkzeugTestCase
@@ -21,6 +25,7 @@
from werkzeug.test import create_environ, Client
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import RequestEntityTooLarge
+from werkzeug._internal import _b
@Request.application
@@ -29,16 +34,16 @@ def form_data_consumer(request):
if result_object == 'text':
return Response(repr(request.form['text']))
f = request.files[result_object]
- return Response('\n'.join((
- repr(f.filename),
- repr(f.name),
- repr(f.content_type),
+ return Response(_b('\n').join((
+ _b(repr(f.filename)),
+ _b(repr(f.name)),
+ _b(repr(f.content_type)),
f.stream.read()
)))
def get_contents(filename):
- f = file(filename, 'rb')
+ f = open(filename, 'rb')
try:
return f.read()
finally:
@@ -48,54 +53,54 @@ def get_contents(filename):
class FormParserTestCase(WerkzeugTestCase):
def test_limiting(self):
- data = 'foo=Hello+World&bar=baz'
- req = Request.from_values(input_stream=StringIO(data),
+ data = _b('foo=Hello+World&bar=baz')
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='application/x-www-form-urlencoded',
method='POST')
req.max_content_length = 400
self.assert_equal(req.form['foo'], 'Hello World')
- req = Request.from_values(input_stream=StringIO(data),
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='application/x-www-form-urlencoded',
method='POST')
req.max_form_memory_size = 7
self.assert_raises(RequestEntityTooLarge, lambda: req.form['foo'])
- req = Request.from_values(input_stream=StringIO(data),
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='application/x-www-form-urlencoded',
method='POST')
req.max_form_memory_size = 400
self.assert_equal(req.form['foo'], 'Hello World')
- data = ('--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
- 'Hello World\r\n'
- '--foo\r\nContent-Disposition: form-field; name=bar\r\n\r\n'
- 'bar=baz\r\n--foo--')
- req = Request.from_values(input_stream=StringIO(data),
+ data = _b('--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
+ 'Hello World\r\n'
+ '--foo\r\nContent-Disposition: form-field; name=bar\r\n\r\n'
+ 'bar=baz\r\n--foo--')
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='multipart/form-data; boundary=foo',
method='POST')
req.max_content_length = 4
self.assert_raises(RequestEntityTooLarge, lambda: req.form['foo'])
- req = Request.from_values(input_stream=StringIO(data),
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='multipart/form-data; boundary=foo',
method='POST')
req.max_content_length = 400
self.assert_equal(req.form['foo'], 'Hello World')
- req = Request.from_values(input_stream=StringIO(data),
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='multipart/form-data; boundary=foo',
method='POST')
req.max_form_memory_size = 7
self.assert_raises(RequestEntityTooLarge, lambda: req.form['foo'])
- req = Request.from_values(input_stream=StringIO(data),
+ req = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='multipart/form-data; boundary=foo',
method='POST')
@@ -114,7 +119,7 @@ def test_parse_form_data_put_without_content(self):
del env['CONTENT_LENGTH']
stream, form, files = formparser.parse_form_data(env)
- self.assert_equal(stream.read(), '')
+ self.assert_equal(stream.read(), _b(''))
self.assert_equal(len(form), 0)
self.assert_equal(len(files), 0)
@@ -124,17 +129,19 @@ def test_parse_form_data_get_without_content(self):
del env['CONTENT_LENGTH']
stream, form, files = formparser.parse_form_data(env)
- self.assert_equal(stream.read(), '')
+ self.assert_equal(stream.read(), _b(''))
self.assert_equal(len(form), 0)
self.assert_equal(len(files), 0)
def test_large_file(self):
- data = 'x' * (1024 * 600)
- req = Request.from_values(data={'foo': (StringIO(data), 'test.txt')},
+ data = _b('x') * (1024 * 600)
+ req = Request.from_values(data={'foo': (BytesIO(data), 'test.txt')},
method='POST')
# make sure we have a real file here, because we expect to be
# on the disk. > 1024 * 500
- self.assert_(isinstance(req.files['foo'].stream, file))
+ if sys.version_info < (3, ):
+ # XXX: can't test it in Python 3
+ self.assertTrue(isinstance(req.files['foo'].stream, file))
class MultiPartTestCase(WerkzeugTestCase):
@@ -173,15 +180,15 @@ def test_basic(self):
response = client.post('/?object=' + field, data=data, content_type=
'multipart/form-data; boundary="%s"' % boundary,
content_length=len(data))
- lines = response.data.split('\n', 3)
- self.assert_equal(lines[0], repr(filename))
- self.assert_equal(lines[1], repr(field))
- self.assert_equal(lines[2], repr(content_type))
+ lines = response.data.split(_b('\n'), 3)
+ self.assert_equal(lines[0], _b(repr(filename)))
+ self.assert_equal(lines[1], _b(repr(field)))
+ self.assert_equal(lines[2], _b(repr(content_type)))
self.assert_equal(lines[3], get_contents(join(folder, fsname)))
response = client.post('/?object=text', data=data, content_type=
'multipart/form-data; boundary="%s"' % boundary,
content_length=len(data))
- self.assert_equal(response.data, repr(text))
+ self.assert_equal(response.data, _b(repr(text)))
def test_ie7_unc_path(self):
client = Client(form_data_consumer, Response)
@@ -190,28 +197,28 @@ def test_ie7_unc_path(self):
boundary = '---------------------------7da36d1b4a0164'
response = client.post('/?object=cb_file_upload_multiple', data=data, content_type=
'multipart/form-data; boundary="%s"' % boundary, content_length=len(data))
- lines = response.data.split('\n', 3)
+ lines = response.data.split(_b('\n'), 3)
self.assert_equal(lines[0],
- repr(u'Sellersburg Town Council Meeting 02-22-2010doc.doc'))
+ _b(repr(u'Sellersburg Town Council Meeting 02-22-2010doc.doc')))
def test_end_of_file(self):
# This test looks innocent but it was actually timeing out in
# the Werkzeug 0.5 release version (#394)
- data = (
+ data = _b(
'--foo\r\n'
'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n'
'Content-Type: text/plain\r\n\r\n'
'file contents and no end'
)
- data = Request.from_values(input_stream=StringIO(data),
+ data = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='multipart/form-data; boundary=foo',
method='POST')
- self.assert_(not data.files)
- self.assert_(not data.form)
+ self.assertFalse(data.files)
+ self.assertFalse(data.form)
def test_broken(self):
- data = (
+ data = _b(
'--foo\r\n'
'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n'
'Content-Transfer-Encoding: base64\r\n'
@@ -221,8 +228,8 @@ def test_broken(self):
)
_, form, files = formparser.parse_form_data(create_environ(data=data,
method='POST', content_type='multipart/form-data; boundary=foo'))
- self.assert_(not files)
- self.assert_(not form)
+ self.assertFalse(files)
+ self.assertFalse(form)
self.assert_raises(ValueError, formparser.parse_form_data,
create_environ(data=data, method='POST',
@@ -230,42 +237,42 @@ def test_broken(self):
silent=False)
def test_file_no_content_type(self):
- data = (
+ data = _b(
'--foo\r\n'
'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n\r\n'
'file contents\r\n--foo--'
)
- data = Request.from_values(input_stream=StringIO(data),
+ data = Request.from_values(input_stream=BytesIO(data),
content_length=len(data),
content_type='multipart/form-data; boundary=foo',
method='POST')
self.assert_equal(data.files['test'].filename, 'test.txt')
- self.assert_equal(data.files['test'].read(), 'file contents')
+ self.assert_equal(data.files['test'].read(), _b('file contents'))
def test_extra_newline(self):
# this test looks innocent but it was actuall