changeset 4754:7cb92118a93e

updated werkzeug to 0.5.1
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Fri, 10 Jul 2009 01:07:24 +0200
parents 5db0151a2df3
children e50adbb534d8
files MoinMoin/support/werkzeug/__init__.py MoinMoin/support/werkzeug/_internal.py MoinMoin/support/werkzeug/contrib/atom.py MoinMoin/support/werkzeug/contrib/securecookie.py MoinMoin/support/werkzeug/datastructures.py MoinMoin/support/werkzeug/debug/__init__.py MoinMoin/support/werkzeug/http.py MoinMoin/support/werkzeug/routing.py MoinMoin/support/werkzeug/script.py MoinMoin/support/werkzeug/serving.py MoinMoin/support/werkzeug/test.py MoinMoin/support/werkzeug/utils.py MoinMoin/support/werkzeug/wrappers.py
diffstat 13 files changed, 195 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/support/werkzeug/__init__.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/__init__.py	Fri Jul 10 01:07:24 2009 +0200
@@ -17,7 +17,21 @@
 from types import ModuleType
 import sys
 
+# This import magic raises concerns quite often which is why the implementation
+# and motiviation is explained here in detail now.
+#
+# The majority of the functions and classes provided by Werkzeug work on the
+# HTTP and WSGI layer.  There is no useful grouping for those which is why
+# they are all importable from "werkzeug" instead of the modules where they are
+# implemented.  The downside of that is, that now everything would be loaded at
+# once, even if unused.
+#
+# The implementation of a lazy-loading module in this file replaces the
+# werkzeug package when imported from within.  Attribute access to the werkzeug
+# module will then lazily import from the modules that implement the objects.
 
+
+# import mapping to objects in other modules
 all_by_module = {
     'werkzeug.debug':       ['DebuggedApplication'],
     'werkzeug.local':       ['Local', 'LocalManager', 'LocalProxy'],
@@ -78,6 +92,7 @@
     'werkzeug._internal':   ['_easteregg']
 }
 
+# modules that should be imported when accessed as attributes of werkzeug
 attribute_modules = dict.fromkeys(['exceptions', 'routing', 'script'])
 
 
@@ -87,6 +102,11 @@
         object_origins[item] = module
 
 
+#: the cached version of the library.  We get the distribution from
+#: pkg_resources the first time this attribute is accessed.  Because
+#: this operation is quite slow it speeds up importing a lot.
+version = None
+
 class module(ModuleType):
     """Automatically import objects from the modules."""
 
@@ -100,15 +120,28 @@
             __import__('werkzeug.' + name)
         return ModuleType.__getattribute__(self, name)
 
+    def __dir__(self):
+        """Just show what we want to show."""
+        result = list(new_module.__all__)
+        result.extend(('__file__', '__path__', '__doc__', '__all__',
+                       '__docformat__', '__name__', '__path__',
+                       '__package__', '__version__'))
+        return result
+
+    @property
+    def __version__(self):
+        global version
+        if version is None:
+            try:
+                version = __import__('pkg_resources') \
+                          .get_distribution('Werkzeug').version
+            except:
+                version = 'unknown'
+        return version
 
 # keep a reference to this module so that it's not garbage collected
 old_module = sys.modules['werkzeug']
 
-# figure out the version
-try:
-    version = __import__('pkg_resources').get_distribution('Werkzeug').version
-except:
-    version = 'unknown'
 
 # setup the new module and patch it into the dict of loaded modules
 new_module = sys.modules['werkzeug'] = module('werkzeug')
@@ -117,6 +150,5 @@
     '__path__':         __path__,
     '__doc__':          __doc__,
     '__all__':          tuple(object_origins) + tuple(attribute_modules),
-    '__docformat__':    'restructuredtext en',
-    '__version__':      version
+    '__docformat__':    'restructuredtext en'
 })
--- a/MoinMoin/support/werkzeug/_internal.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/_internal.py	Fri Jul 10 01:07:24 2009 +0200
@@ -237,6 +237,21 @@
     )
 
 
+_timegm = None
+def _date_to_unix(arg):
+    """Converts a timetuple, integer or datetime object into the seconds from
+    epoch in utc.
+    """
+    global _timegm
+    if isinstance(arg, datetime):
+        arg = arg.utctimetuple()
+    elif isinstance(arg, (int, long, float)):
+        return int(arg)
+    if _timegm is None:
+        from calendar import timegm as _timegm
+    return _timegm(arg)
+
+
 class _ExtendedMorsel(Morsel):
     _reserved = {'httponly': 'HttpOnly'}
     _reserved.update(Morsel._reserved)
--- a/MoinMoin/support/werkzeug/contrib/atom.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/contrib/atom.py	Fri Jul 10 01:07:24 2009 +0200
@@ -9,8 +9,8 @@
     Example::
 
         def atom_feed(request):
-            feed = AtomFeed("My Blog", feed_url=req.url,
-                            url=req.host_url,
+            feed = AtomFeed("My Blog", feed_url=request.url,
+                            url=request.host_url,
                             subtitle="My example blog for a feed test.")
             for post in Post.query.limit(10).all():
                 feed.add(post.title, post.body, content_type='html',
@@ -170,7 +170,7 @@
                 escape(self.feed_url, True)
         for link in self.links:
             yield u'  <link %s/>\n' % ''.join('%s="%s" ' % \
-                [(k, escape(link[k], True)) for k in link])
+                (k, escape(link[k], True)) for k in link)
         for author in self.author:
             yield u'  <author>\n'
             yield u'    <name>%s</name>\n' % escape(author['name'])
@@ -323,7 +323,7 @@
             yield u'  </author>\n'
         for link in self.links:
             yield u'  <link %s/>\n' % ''.join('%s="%s" ' % \
-                [(k, escape(link[k], True)) for k in link])
+                (k, escape(link[k], True)) for k in link)
         if self.summary:
             yield u'  ' + _make_text_block('summary', self.summary,
                                            self.summary_type)
--- a/MoinMoin/support/werkzeug/contrib/securecookie.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/contrib/securecookie.py	Fri Jul 10 01:07:24 2009 +0200
@@ -93,6 +93,7 @@
 from datetime import datetime
 from time import time, mktime, gmtime
 from werkzeug import url_quote_plus, url_unquote_plus
+from werkzeug._internal import _date_to_unix
 from werkzeug.contrib.sessions import ModificationTrackingDict
 
 
@@ -221,11 +222,7 @@
         if self.secret_key is None:
             raise RuntimeError('no secret key defined')
         if expires:
-            if isinstance(expires, datetime):
-                expires = expires.utctimetuple()
-            elif isinstance(expires, (int, long, float)):
-                expires = gmtime(expires)
-            self['_expires'] = int(mktime(expires))
+            self['_expires'] = _date_to_unix(expires)
         result = []
         mac = hmac(self.secret_key, None, self.hash_method)
         for key, value in sorted(self.items()):
--- a/MoinMoin/support/werkzeug/datastructures.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/datastructures.py	Fri Jul 10 01:07:24 2009 +0200
@@ -196,6 +196,16 @@
     .. versionadded:: 0.5
     """
 
+    def copy(self):
+        """Return a shallow mutable copy of this object.  Keep in mind that
+        the standard library's :func:`copy` funciton is a no-op for this class
+        like for any other python immutable type (eg: :class:`tuple`).
+        """
+        return TypeConversionDict(self)
+
+    def __copy__(self):
+        return self
+
 
 class MultiDict(TypeConversionDict):
     """A :class:`MultiDict` is a dictionary subclass customized to deal with
@@ -245,7 +255,7 @@
 
     def __init__(self, mapping=None):
         if isinstance(mapping, MultiDict):
-            dict.__init__(self, ((k, l[:]) for k, l in mapping.lists()))
+            dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
         elif isinstance(mapping, dict):
             tmp = {}
             for key, value in mapping.iteritems():
@@ -358,13 +368,17 @@
             default_list = dict.__getitem__(self, key)
         return default_list
 
-    def items(self):
-        """Return a list of ``(key, value)`` pairs, where value is the first
-        item in the list associated with the key.
+    def items(self, multi=False):
+        """Return a list of ``(key, value)`` pairs.
+
+        :param multi: If set to `True` the list returned will have a
+                      pair for each value of each key.  Ohterwise it
+                      will only contain pairs for the first value of
+                      each key.
 
         :return: a :class:`list`
         """
-        return [(key, self[key]) for key in self.iterkeys()]
+        return list(self.iteritems(multi))
 
     #: Return a list of ``(key, value)`` pairs, where values is the list of
     #: all values associated with the key.
@@ -386,15 +400,19 @@
         >>> d = MultiDict({"foo": [1, 2, 3]})
         >>> zip(d.keys(), d.listvalues()) == d.lists()
         True
-        
+
         :return: a :class:`list`
         """
         return list(self.iterlistvalues())
 
-    def iteritems(self):
+    def iteritems(self, multi=False):
         """Like :meth:`items` but returns an iterator."""
         for key, values in dict.iteritems(self):
-            yield key, values[0]
+            if multi:
+                for value in values:
+                    yield key, value
+            else:
+                yield key, values[0]
 
     def iterlists(self):
         """Return a list of all values associated with a key.
@@ -491,11 +509,7 @@
             raise self.KeyError(str(e))
 
     def __repr__(self):
-        tmp = []
-        for key, values in self.iterlists():
-            for value in values:
-                tmp.append((key, value))
-        return '%s(%r)' % (self.__class__.__name__, tmp)
+        return '%s(%r)' % (self.__class__.__name__, self.items(multi=True))
 
 
 class Headers(object):
@@ -977,11 +991,13 @@
             rv.update(d.keys())
         return list(rv)
 
-    def iteritems(self):
+    def iteritems(self, multi=False):
         found = set()
         for d in self.dicts:
-            for key, value in d.iteritems():
-                if key not in found:
+            for key, value in d.iteritems(multi):
+                if multi:
+                    yield key, value
+                elif key not in found:
                     found.add(key)
                     yield key, value
 
@@ -992,8 +1008,8 @@
     def values(self):
         return list(self.itervalues())
 
-    def items(self):
-        return list(self.iteritems())
+    def items(self, multi=False):
+        return list(self.iteritems(multi))
 
     def iterlists(self):
         rv = {}
@@ -1089,6 +1105,16 @@
 
     __repr__ = _proxy_repr(dict)
 
+    def copy(self):
+        """Return a shallow mutable copy of this object.  Keep in mind that
+        the standard library's :func:`copy` funciton is a no-op for this class
+        like for any other python immutable type (eg: :class:`tuple`).
+        """
+        return dict(self)
+
+    def __copy__(self):
+        return self
+
 
 class ImmutableMultiDict(ImmutableMultiDictMixin, MultiDict):
     """An immutable :class:`MultiDict`.
@@ -1096,6 +1122,16 @@
     .. versionadded:: 0.5
     """
 
+    def copy(self):
+        """Return a shallow mutable copy of this object.  Keep in mind that
+        the standard library's :func:`copy` funciton is a no-op for this class
+        like for any other python immutable type (eg: :class:`tuple`).
+        """
+        return MultiDict(self)
+
+    def __copy__(self):
+        return self
+
 
 class Accept(ImmutableList):
     """An :class:`Accept` object is just a list subclass for lists of
--- a/MoinMoin/support/werkzeug/debug/__init__.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/debug/__init__.py	Fri Jul 10 01:07:24 2009 +0200
@@ -55,7 +55,7 @@
     def __init__(self, app, evalex=False, request_key='werkzeug.request',
                  console_path='/console', console_init_func=None,
                  show_hidden_frames=False):
-        if console_init_func:
+        if not console_init_func:
             console_init_func = dict
         self.app = app
         self.evalex = evalex
--- a/MoinMoin/support/werkzeug/http.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/http.py	Fri Jul 10 01:07:24 2009 +0200
@@ -517,8 +517,21 @@
     iterator = chain(make_line_iter(file, buffer_size=buffer_size),
                      repeat(''))
 
+    def _find_terminator():
+        """The terminator might have some additional newlines before it.
+        There is at least one application that sends additional newlines
+        before headers (the python setuptools package).
+        """
+        for line in iterator:
+            if not line:
+                break
+            line = line.strip()
+            if line:
+                return line
+        return ''
+
     try:
-        terminator = iterator.next().strip()
+        terminator = _find_terminator()
         if terminator != next_part:
             raise ValueError('Expected boundary at start of multipart data')
 
@@ -553,8 +566,10 @@
             else:
                 stream = StringIO()
 
-            newline_length = 0
+            buf = ''
             for line in iterator:
+                if not line:
+                    raise ValueError('unexpected end of stream')
                 if line[:2] == '--':
                     terminator = line.rstrip()
                     if terminator in (next_part, last_part):
@@ -564,8 +579,21 @@
                         line = line.decode(transfer_encoding)
                     except:
                         raise ValueError('could not base 64 decode chunk')
+                # we have something in the buffer from the last iteration.
+                # write that value to the output stream now and clear the buffer.
+                if buf:
+                    stream.write(buf)
+                    buf = ''
+
+                # If the line ends with windows CRLF we write everything except
+                # the last two bytes.  In all other cases however we write everything
+                # except the last byte.  If it was a newline, that's fine, otherwise
+                # it does not matter because we write it the last iteration.  If the
+                # loop aborts early because the end of a part was reached, the last
+                # newline is not written which is exactly what we want.
                 newline_length = line[-2:] == '\r\n' and 2 or 1
-                stream.write(line)
+                stream.write(line[:-newline_length])
+                buf = line[-newline_length:]
                 if not is_file and max_form_memory_size is not None:
                     in_memory += len(line)
                     if in_memory > max_form_memory_size:
@@ -574,9 +602,7 @@
             else:
                 raise ValueError('unexpected end of part')
 
-            # chop off the trailing line terminator and rewind
-            stream.seek(-newline_length, 1)
-            stream.truncate()
+            # rewind the stream
             stream.seek(0)
 
             if is_file:
@@ -710,5 +736,6 @@
      WWWAuthenticate
 
 
+# DEPRECATED
 # backwards compatibible imports
 from werkzeug.datastructures import MIMEAccept, CharsetAccept, LanguageAccept
--- a/MoinMoin/support/werkzeug/routing.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/routing.py	Fri Jul 10 01:07:24 2009 +0200
@@ -1215,7 +1215,7 @@
                     self.subdomain and self.subdomain + '.' or '',
                     self.server_name,
                     self.script_name[:-1],
-                    path_info.lstrip('/')
+                    url_quote(path_info.lstrip('/'), self.map.charset)
                 )))
             if rv is None:
                 continue
@@ -1233,7 +1233,7 @@
                             subdomain and subdomain + '.' or '',
                             self.server_name,
                             self.script_name[:-1],
-                            path.lstrip('/')
+                            url_quote(path.lstrip('/'), self.map.charset)
                         )))
             if rule.redirect_to is not None:
                 if isinstance(rule.redirect_to, basestring):
--- a/MoinMoin/support/werkzeug/script.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/script.py	Fri Jul 10 01:07:24 2009 +0200
@@ -294,5 +294,6 @@
         from werkzeug.serving import run_simple
         app = app_factory()
         run_simple(hostname, port, app, reloader, debugger, evalex,
-                   extra_files, 1, threaded, processes, static_files)
+                   extra_files, 1, threaded, processes,
+                   static_files=static_files)
     return action
--- a/MoinMoin/support/werkzeug/serving.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/serving.py	Fri Jul 10 01:07:24 2009 +0200
@@ -382,7 +382,7 @@
         _log('info', ' * Running on http://%s:%d/', display_hostname, port)
     if use_reloader:
         # Create and destroy a socket so that any exceptions are raised before
-        # we spawn a separate Python interpreter and loose this ability.
+        # we spawn a separate Python interpreter and lose this ability.
         test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         test_socket.bind((hostname, port))
--- a/MoinMoin/support/werkzeug/test.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/test.py	Fri Jul 10 01:07:24 2009 +0200
@@ -91,7 +91,6 @@
 
     length = int(_closure[0].tell())
     _closure[0].seek(0)
-    _closure[0].seek(0)
     return _closure[0], length, boundary
 
 
@@ -121,8 +120,9 @@
 
     def getheaders(self, name):
         headers = []
+        name = name.lower()
         for k, v in self.headers:
-            if k == name:
+            if k.lower() == name:
                 headers.append(v)
         return headers
 
--- a/MoinMoin/support/werkzeug/utils.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/utils.py	Fri Jul 10 01:07:24 2009 +0200
@@ -81,6 +81,9 @@
         except:
             pass
 
+    def __nonzero__(self):
+        return bool(self.filename)
+
     def __getattr__(self, name):
         return getattr(self.stream, name)
 
@@ -189,6 +192,7 @@
         manager = ResourceManager()
         filesystem_bound = isinstance(provider, DefaultProvider)
         def loader(path):
+            path = posixpath.join(package_path, path)
             if path is None or not provider.has_resource(path):
                 return None, None
             basename = posixpath.basename(path)
@@ -813,7 +817,8 @@
 
 def parse_form_data(environ, stream_factory=None, charset='utf-8',
                     errors='ignore', max_form_memory_size=None,
-                    max_content_length=None, cls=None):
+                    max_content_length=None, cls=None,
+                    silent=True):
     """Parse the form data in the environ and return it as tuple in the form
     ``(stream, form, files)``.  You should only call this method if the
     transport method is `POST` or `PUT`.
@@ -832,6 +837,9 @@
        The `max_form_memory_size`, `max_content_length` and
        `cls` parameters were added.
 
+    .. versionadded:: 0.5.1
+       The optional `silent` flag was added.
+
     :param environ: the WSGI environment to be used for parsing.
     :param stream_factory: An optional callable that returns a new read and
                            writeable file descriptor.  This callable works
@@ -849,6 +857,7 @@
                                exception is raised.
     :param cls: an optional dict class to use.  If this is not specified
                        or `None` the default :class:`MultiDict` is used.
+    :param silent: If set to False parsing errors will not be catched.
     :return: A tuple in the form ``(stream, form, files)``.
     """
     content_type, extra = parse_options_header(environ.get('CONTENT_TYPE', ''))
@@ -874,6 +883,8 @@
                                           charset, errors,
                                           max_form_memory_size=max_form_memory_size)
         except ValueError, e:
+            if not silent:
+                raise
             form = cls()
         else:
             form = cls(form)
--- a/MoinMoin/support/werkzeug/wrappers.py	Sat Jul 04 16:22:01 2009 +0200
+++ b/MoinMoin/support/werkzeug/wrappers.py	Fri Jul 10 01:07:24 2009 +0200
@@ -147,7 +147,7 @@
         charset = kwargs.pop('charset', cls.charset)
         environ = kwargs.pop('environ', None)
         if environ is not None:
-            from warnings import DeprecationWarning
+            from warnings import warn
             warn(DeprecationWarning('The environ parameter to from_values'
                                     ' is now called environ_overrides for'
                                     ' consistency with EnvironBuilder'),
@@ -221,22 +221,38 @@
                 raise RuntimeError('A shallow request tried to consume '
                                    'form data.  If you really want to do '
                                    'that, set `shallow` to False.')
+            data = None
             if self.environ['REQUEST_METHOD'] in ('POST', 'PUT'):
-                data = parse_form_data(self.environ, self._get_file_stream,
-                                       self.charset, self.encoding_errors,
-                                       self.max_form_memory_size,
-                                       self.max_content_length,
-                                       cls=ImmutableMultiDict)
-            else:
+                try:
+                    data = parse_form_data(self.environ, self._get_file_stream,
+                                           self.charset, self.encoding_errors,
+                                           self.max_form_memory_size,
+                                           self.max_content_length,
+                                           cls=ImmutableMultiDict,
+                                           silent=False)
+                except ValueError, e:
+                    self._form_parsing_failed(e)
+            if data is None:
                 data = (_empty_stream, ImmutableMultiDict(),
                         ImmutableMultiDict())
             self._data_stream, self._form, self._files = data
 
+    def _form_parsing_failed(self, error):
+        """Called if parsing of form data failed.  This is currently only
+        invoked for failed multipart uploads.  By default this method does
+        nothing.
+
+        :param error: a `ValueError` object with a message why the
+                      parsing failed.
+
+        .. versionadded:: 0.5.1
+        """
+
     @property
     def stream(self):
         """The parsed stream if the submitted data was not multipart or
-        urlencoded form data.  This stream is the stream left by the CGI
-        module after parsing.  This is *not* the WSGI input stream but
+        urlencoded form data.  This stream is the stream left by the form data
+        parser module after parsing.  This is *not* the WSGI input stream but
         a wrapper around it that ensures the caller does not accidentally
         read past `Content-Length`.
         """
@@ -249,7 +265,7 @@
 
     @cached_property
     def args(self):
-        """The parsed URL parameters as :class:`MultiDict`."""
+        """The parsed URL parameters as :class:`ImmutableMultiDict`."""
         return url_decode(self.environ.get('QUERY_STRING', ''), self.charset,
                           errors=self.encoding_errors,
                           cls=ImmutableMultiDict)
@@ -268,8 +284,8 @@
     @property
     def form(self):
         """Form parameters.  Currently it's not guaranteed that the
-        :class:`MultiDict` returned by this function is ordered in the same
-        way as the submitted form data.
+        :class:`ImmutableMultiDict` returned by this function is ordered in
+        the same way as the submitted form data.
         """
         self._load_form_data()
         return self._form