Flask update

This commit is contained in:
Ruud
2011-08-24 20:59:59 +02:00
parent 970b400a0c
commit d7f758dff4
90 changed files with 3328 additions and 1236 deletions
+254 -22
View File
@@ -13,11 +13,11 @@
module.
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import re
import inspect
from time import time
try:
from email.utils import parsedate_tz
except ImportError: # pragma: no cover
@@ -32,7 +32,8 @@ except ImportError: # pragma: no cover
#: 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, _dump_date, \
_ExtendedCookie, _ExtendedMorsel, _decode_unicode
_accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
@@ -284,13 +285,14 @@ def parse_cache_control_header(value, on_update=None, cls=None):
.. versionadded:: 0.5
The `cls` was added. If not specified an immutable
:class:`RequestCacheControl` is returned.
:class:`~werkzeug.datastructures.RequestCacheControl` is returned.
:param value: a cache control header to be parsed.
:param on_update: an optional callable that is called every time a
value on the :class:`CacheControl` object is changed.
:param on_update: an optional callable that is called every time a value
on the :class:`~werkzeug.datastructures.CacheControl`
object is changed.
:param cls: the class for the returned object. By default
:class:`RequestCacheControl` is used.
:class:`~werkzeug.datastructures.RequestCacheControl` is used.
:return: a `cls` object.
"""
if cls is None:
@@ -301,7 +303,8 @@ def parse_cache_control_header(value, on_update=None, cls=None):
def parse_set_header(value, on_update=None):
"""Parse a set-like header and return a :class:`HeaderSet` object:
"""Parse a set-like header and return a
:class:`~werkzeug.datastructures.HeaderSet` object:
>>> hs = parse_set_header('token, "quoted value"')
@@ -320,8 +323,9 @@ def parse_set_header(value, on_update=None):
:param value: a set header to be parsed.
:param on_update: an optional callable that is called every time a
value on the :class:`HeaderSet` object is changed.
:return: a :class:`HeaderSet`
value on the :class:`~werkzeug.datastructures.HeaderSet`
object is changed.
:return: a :class:`~werkzeug.datastructures.HeaderSet`
"""
if not value:
return HeaderSet(None, on_update)
@@ -331,10 +335,11 @@ def parse_set_header(value, on_update=None):
def parse_authorization_header(value):
"""Parse an HTTP basic/digest authorization header transmitted by the web
browser. The return value is either `None` if the header was invalid or
not given, otherwise an :class:`Authorization` object.
not given, otherwise an :class:`~werkzeug.datastructures.Authorization`
object.
:param value: the authorization header to parse.
:return: a :class:`Authorization` object or `None`.
:return: a :class:`~werkzeug.datastructures.Authorization` object or `None`.
"""
if not value:
return
@@ -360,13 +365,14 @@ def parse_authorization_header(value):
def parse_www_authenticate_header(value, on_update=None):
"""Parse an HTTP WWW-Authenticate header into a :class:`WWWAuthenticate`
object.
"""Parse an HTTP WWW-Authenticate header into a
:class:`~werkzeug.datastructures.WWWAuthenticate` object.
:param value: a WWW-Authenticate header to parse.
:param on_update: an optional callable that is called every time a
value on the :class:`WWWAuthenticate` object is changed.
:return: a :class:`WWWAuthenticate` object.
:param on_update: an optional callable that is called every time a value
on the :class:`~werkzeug.datastructures.WWWAuthenticate`
object is changed.
:return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object.
"""
if not value:
return WWWAuthenticate(on_update=on_update)
@@ -379,6 +385,109 @@ def parse_www_authenticate_header(value, on_update=None):
on_update)
def parse_if_range_header(value):
"""Parses an if-range header which can be an etag or a date. Returns
a :class:`~werkzeug.datastructures.IfRange` object.
.. versionadded:: 0.7
"""
if not value:
return IfRange()
date = parse_date(value)
if date is not None:
return IfRange(date=date)
# drop weakness information
return IfRange(unquote_etag(value)[0])
def parse_range_header(value, make_inclusive=True):
"""Parses a range header into a :class:`~werkzeug.datastructures.Range`
object. If the header is missing or malformed `None` is returned.
`ranges` is a list of ``(start, stop)`` tuples where the ranges are
non-inclusive.
.. versionadded:: 0.7
"""
if not value or '=' not in value:
return None
ranges = []
last_end = 0
units, rng = value.split('=', 1)
units = units.strip().lower()
for item in rng.split(','):
item = item.strip()
if '-' not in item:
return None
if item.startswith('-'):
if last_end < 0:
return None
begin = int(item)
end = None
last_end = -1
elif '-' in item:
begin, end = item.split('-', 1)
begin = int(begin)
if begin < last_end or last_end < 0:
return None
if end:
end = int(end) + 1
if begin >= end:
return None
else:
end = None
last_end = end
ranges.append((begin, end))
return Range(units, ranges)
def parse_content_range_header(value, on_update=None):
"""Parses a range header into a
:class:`~werkzeug.datastructures.ContentRange` object or `None` if
parsing is not possible.
.. versionadded:: 0.7
:param value: a content range header to be parsed.
:param on_update: an optional callable that is called every time a value
on the :class:`~werkzeug.datastructures.ContentRange`
object is changed.
"""
if value is None:
return None
try:
units, rangedef = (value or '').strip().split(None, 1)
except ValueError:
return None
if '/' not in rangedef:
return None
rng, length = rangedef.split('/', 1)
if length == '*':
length = None
elif length.isdigit():
length = int(length)
else:
return None
if rng == '*':
return ContentRange(units, None, None, length, on_update=on_update)
elif '-' not in rng:
return None
start, stop = rng.split('-', 1)
try:
start = int(start)
stop = int(stop) + 1
except ValueError:
return None
if is_byte_range_valid(start, stop, length):
return ContentRange(units, start, stop, length, on_update=on_update)
def quote_etag(etag, weak=False):
"""Quote an etag.
@@ -420,7 +529,7 @@ def parse_etags(value):
"""Parse an etag header.
:param value: the tag header to parse
:return: an :class:`ETags` object.
:return: an :class:`~werkzeug.datastructures.ETags` object.
"""
if not value:
return ETags()
@@ -532,6 +641,12 @@ def is_resource_modified(environ, etag=None, data=None, last_modified=None):
unmodified = False
if isinstance(last_modified, basestring):
last_modified = parse_date(last_modified)
# ensure that microsecond is zero because the HTTP spec does not transmit
# that either and we might have some false positives. See issue #39
if last_modified is not None:
last_modified = last_modified.replace(microsecond=0)
modified_since = parse_date(environ.get('HTTP_IF_MODIFIED_SINCE'))
if modified_since and last_modified and last_modified <= modified_since:
@@ -596,12 +711,129 @@ def is_hop_by_hop_header(header):
return header.lower() in _hop_by_pop_headers
def parse_cookie(header, charset='utf-8', errors='replace',
cls=None):
"""Parse a cookie. Either from a string or WSGI environ.
Per default encoding errors are ignored. If you want a different behavior
you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
:exc:`HTTPUnicodeError` is raised.
.. versionchanged:: 0.5
This function now returns a :class:`TypeConversionDict` instead of a
regular dict. The `cls` parameter was added.
:param header: the header to be used to parse the cookie. Alternatively
this can be a WSGI environment.
:param charset: the charset for the cookie values.
:param errors: the error behavior for the charset decoding.
:param cls: an optional dict class to use. If this is not specified
or `None` the default :class:`TypeConversionDict` is
used.
"""
if isinstance(header, dict):
header = header.get('HTTP_COOKIE', '')
if cls is None:
cls = TypeConversionDict
cookie = _ExtendedCookie()
cookie.load(header)
result = {}
# decode to unicode and skip broken items. Our extended morsel
# and extended cookie will catch CookieErrors and convert them to
# `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)
return cls(result)
def dump_cookie(key, value='', max_age=None, expires=None, path='/',
domain=None, secure=None, httponly=False, charset='utf-8',
sync_expires=True):
"""Creates a new Set-Cookie header without the ``Set-Cookie`` prefix
The parameters are the same as in the cookie Morsel object in the
Python standard library but it accepts unicode data, too.
:param max_age: should be a number of seconds, or `None` (default) if
the cookie should last only as long as the client's
browser session. Additionally `timedelta` objects
are accepted, too.
:param expires: should be a `datetime` object or unix timestamp.
:param path: limits the cookie to a given path, per default it will
span the whole domain.
:param domain: Use this if you want to set a cross-domain cookie. For
example, ``domain=".example.com"`` will set a cookie
that is readable by the domain ``www.example.com``,
``foo.example.com`` etc. Otherwise, a cookie will only
be readable by the domain that set it.
:param secure: The cookie will only be available via HTTPS
:param httponly: disallow JavaScript to access the cookie. This is an
extension to the cookie standard and probably not
supported by all browsers.
:param charset: the encoding for unicode values.
:param sync_expires: automatically set expires if max_age is defined
but expires not.
"""
try:
key = str(key)
except UnicodeError:
raise TypeError('invalid key %r' % key)
if isinstance(value, unicode):
value = value.encode(charset)
value = quote_header_value(value)
morsel = _ExtendedMorsel(key, value)
if isinstance(max_age, timedelta):
max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds
if expires is not None:
if not isinstance(expires, basestring):
expires = cookie_date(expires)
morsel['expires'] = expires
elif max_age is not None and sync_expires:
morsel['expires'] = cookie_date(time() + max_age)
if domain and ':' in domain:
# The port part of the domain should NOT be used. Strip it
domain = domain.split(':', 1)[0]
if domain:
assert '.' in domain, (
"Setting \"domain\" for a cookie on a server running localy (ex: "
"localhost) is not supportted by complying browsers. You should "
"have something like: \"127.0.0.1 localhost dev.localhost\" on "
"your hosts file and then point your server to run on "
"\"dev.localhost\" and also set \"domain\" for \"dev.localhost\""
)
for k, v in (('path', path), ('domain', domain), ('secure', secure),
('max-age', max_age), ('httponly', httponly)):
if v is not None and v is not False:
morsel[k] = str(v)
return morsel.output(header='').lstrip()
def is_byte_range_valid(start, stop, length):
"""Checks if a given byte content range is valid for the given length.
.. versionadded:: 0.7
"""
if (start is None) != (stop is None):
return False
elif start is None:
return length is None or length >= 0
elif length is None:
return 0 <= start < stop
elif start >= stop:
return False
return 0 <= start < length
# circular dependency fun
from werkzeug.datastructures import Headers, Accept, RequestCacheControl, \
ResponseCacheControl, HeaderSet, ETags, Authorization, \
WWWAuthenticate
from werkzeug.datastructures import Accept, HeaderSet, ETags, Authorization, \
WWWAuthenticate, TypeConversionDict, IfRange, Range, ContentRange, \
RequestCacheControl
# DEPRECATED
# backwards compatible imports
from werkzeug.datastructures import MIMEAccept, CharsetAccept, LanguageAccept
from werkzeug.datastructures import MIMEAccept, CharsetAccept, \
LanguageAccept, Headers