diff options
Diffstat (limited to 'webob')
-rw-r--r-- | webob/cachecontrol.py | 42 | ||||
-rw-r--r-- | webob/cookies.py | 25 | ||||
-rw-r--r-- | webob/datetime_utils.py | 10 | ||||
-rw-r--r-- | webob/dec.py | 10 | ||||
-rw-r--r-- | webob/descriptors.py | 9 | ||||
-rw-r--r-- | webob/exc.py | 6 | ||||
-rw-r--r-- | webob/multidict.py | 21 | ||||
-rw-r--r-- | webob/request.py | 197 | ||||
-rw-r--r-- | webob/response.py | 153 |
9 files changed, 322 insertions, 151 deletions
diff --git a/webob/cachecontrol.py b/webob/cachecontrol.py index 59cc2aa..ba4df4e 100644 --- a/webob/cachecontrol.py +++ b/webob/cachecontrol.py @@ -1,15 +1,18 @@ """ Represents the Cache-Control header """ - import re + class UpdateDict(dict): """ - Dict that has a callback on all updates + Dict that has a callback on all updates """ + + #@@ should these really be module globals? updated = None updated_args = None + def _updated(self): """ Assign to new_dict.updated to track updates @@ -20,36 +23,46 @@ class UpdateDict(dict): if args is None: args = (self,) updated(*args) + def __setitem__(self, key, item): dict.__setitem__(self, key, item) self._updated() + def __delitem__(self, key): dict.__delitem__(self, key) self._updated() + def clear(self): dict.clear(self) self._updated() + def update(self, *args, **kw): dict.update(self, *args, **kw) self._updated() + def setdefault(self, key, failobj=None): - dict.setdefault(self, key, failobj) - self._updated() - def pop(self): - v = dict.pop(self) - self._updated() + val = dict.setdefault(self, key, failobj) + if val is failobj: + self._updated() + return val + + def pop(self, default=None): + v = dict.pop(self, default) + if v is not default: + self._updated() return v + def popitem(self): v = dict.popitem(self) self._updated() return v - token_re = re.compile( r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?') need_quote_re = re.compile(r'[^a-zA-Z0-9._-]') + class exists_property(object): """ Represents a property that either is listed in the Cache-Control @@ -63,19 +76,23 @@ class exists_property(object): if obj is None: return self return self.prop in obj.properties + def __set__(self, obj, value): if (self.type is not None and self.type != obj.type): raise AttributeError( "The property %s only applies to %s Cache-Control" % (self.prop, self.type)) + if value: obj.properties[self.prop] = None else: if self.prop in obj.properties: del obj.properties[self.prop] + def __delete__(self, obj): self.__set__(obj, False) + class value_property(object): """ Represents a property that has a value in the Cache-Control header. @@ -87,6 +104,7 @@ class value_property(object): self.default = default self.none = none self.type = type + def __get__(self, obj, type=None): if obj is None: return self @@ -98,6 +116,7 @@ class value_property(object): return value else: return self.default + def __set__(self, obj, value): if (self.type is not None and self.type != obj.type): @@ -110,10 +129,12 @@ class value_property(object): obj.properties[self.prop] = None # Empty value, but present else: obj.properties[self.prop] = value + def __delete__(self, obj): if self.prop in obj.properties: del obj.properties[self.prop] + class CacheControl(object): """ @@ -124,6 +145,8 @@ class CacheControl(object): only apply to requests or responses). """ + update_dict = UpdateDict + def __init__(self, properties, type): self.properties = properties self.type = type @@ -137,7 +160,7 @@ class CacheControl(object): ``updates_to``, if that is given. """ if updates_to: - props = UpdateDict() + props = cls.update_dict() props.updated = updates_to else: props = {} @@ -188,6 +211,7 @@ class CacheControl(object): """ return self.__class__(self.properties.copy(), type=self.type) + def serialize_cache_control(properties): if isinstance(properties, CacheControl): properties = properties.properties diff --git a/webob/cookies.py b/webob/cookies.py index 0536725..b602057 100644 --- a/webob/cookies.py +++ b/webob/cookies.py @@ -1,5 +1,9 @@ -import re, time, string -from datetime import datetime, date, timedelta +import re +import time +import string +from datetime import datetime +from datetime import date +from datetime import timedelta __all__ = ['Cookie'] @@ -36,9 +40,8 @@ class Cookie(dict): __str__ = serialize def __repr__(self): - return '<%s: [%s]>' % (self.__class__.__name__, ', '.join(map(repr, self.values()))) - - + return '<%s: [%s]>' % (self.__class__.__name__, + ', '.join(map(repr, self.values()))) def cookie_property(key, serialize=lambda v: v): @@ -113,7 +116,8 @@ class Morsel(dict): __str__ = serialize def __repr__(self): - return '<%s: %s=%s>' % (self.__class__.__name__, self.name, repr(self.value)) + return '<%s: %s=%s>' % (self.__class__.__name__, + self.name, repr(self.value)) _c_renames = { "expires" : "expires", @@ -135,7 +139,8 @@ _c_keys.update(['secure', 'httponly']) _re_quoted = r'"(?:[^\"]|\.)*"' # any doublequoted string _legal_special_chars = "~!@#$%^&*()_+=-`.?|:/(){}<>'" -_re_legal_char = r"[\w\d%s]" % ''.join(map(r'\%s'.__mod__, _legal_special_chars)) +_re_legal_char = r"[\w\d%s]" % ''.join(map(r'\%s'.__mod__, + _legal_special_chars)) _re_expires_val = r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT" _rx_cookie = re.compile( # key @@ -169,7 +174,8 @@ _trans_noop = ''.join(chr(x) for x in xrange(256)) # these chars can be in cookie value w/o causing it to be quoted _no_escape_special_chars = "!#$%&'*+-.^_`|~/" -_no_escape_chars = string.ascii_letters + string.digits + _no_escape_special_chars +_no_escape_chars = string.ascii_letters + string.digits + \ + _no_escape_special_chars # these chars never need to be quoted _escape_noop_chars = _no_escape_chars+':, ' # this is a map used to escape the values @@ -181,7 +187,8 @@ _escape_char = _escape_map.__getitem__ weekdays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') -months = (None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') +months = (None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec') def needs_quoting(v): diff --git a/webob/datetime_utils.py b/webob/datetime_utils.py index 1874c1a..820a34f 100644 --- a/webob/datetime_utils.py +++ b/webob/datetime_utils.py @@ -10,6 +10,12 @@ __all__ = [ 'parse_date_delta', 'serialize_date_delta', ] +_NOW = None # hook point for unit tests +def _now(): + if _NOW is not None: + return _NOW + return datetime.now() + class _UTC(tzinfo): def dst(self, dt): return timedelta(0) @@ -63,7 +69,7 @@ def serialize_date(dt): if isinstance(dt, str): return dt if isinstance(dt, timedelta): - dt = datetime.now() + dt + dt = _now() + dt if isinstance(dt, (datetime, date)): dt = dt.timetuple() if isinstance(dt, (tuple, time.struct_time)): @@ -86,7 +92,7 @@ def parse_date_delta(value): except ValueError: return parse_date(value) else: - return datetime.now() + timedelta(seconds=value) + return _now() + timedelta(seconds=value) def serialize_date_delta(value): diff --git a/webob/dec.py b/webob/dec.py index 8707bb1..f14194b 100644 --- a/webob/dec.py +++ b/webob/dec.py @@ -67,7 +67,7 @@ class wsgify(object): * A string, which will be written to ``req.response`` and then that response will be used. * Raise an exception from :mod:`webob.exc` - + Also see :func:`wsgify.middleware` for a way to make middleware. You can also subclass this decorator; the most useful things to do @@ -75,7 +75,7 @@ class wsgify(object): `call_func` (e.g., to add ``req.urlvars`` as keyword arguments to the function). """ - + RequestClass = webob.Request def __init__(self, func=None, RequestClass=None, @@ -98,7 +98,7 @@ class wsgify(object): if self.RequestClass is not self.__class__.RequestClass: args.append('RequestClass=%r' % self.RequestClass) if self.args: - args.append('args=%r' % self.args) + args.append('args=%r' % (self.args,)) my_name = self.__class__.__name__ if self.middleware_wraps is not None: my_name = '%s.middleware' % my_name @@ -275,7 +275,7 @@ class _UnboundMiddleware(object): middleware function; the intermediate object when you do something like ``@wsgify.middleware(RequestClass=Foo)`` """ - + def __init__(self, wrapper_class, app, kw): self.wrapper_class = wrapper_class self.app = app @@ -297,7 +297,7 @@ class _MiddlewareFactory(object): """A middleware that has not yet been bound to an application or configured. """ - + def __init__(self, wrapper_class, middleware, kw): self.wrapper_class = wrapper_class self.middleware = middleware diff --git a/webob/descriptors.py b/webob/descriptors.py index d037ebc..f1a5eeb 100644 --- a/webob/descriptors.py +++ b/webob/descriptors.py @@ -1,5 +1,7 @@ +# -*- coding: utf-8 -*- + import warnings -import re, textwrap +import re from datetime import datetime, date from webob.byterange import Range, ContentRange @@ -76,9 +78,10 @@ def header_getter(header, rfc_section): def converter(prop, parse, serialize, convert_name=None): assert isinstance(prop, property) - convert_name = convert_name or "%r and %r" % (parse, serialize) + convert_name = convert_name or "%r and %r" % (parse.__name__, + serialize.__name__) doc = prop.__doc__ or '' - doc += " Converts it as a %s." % convert_name + doc += " Converts it using %s." % convert_name hget, hset = prop.fget, prop.fset def fget(r): return parse(hget(r)) diff --git a/webob/exc.py b/webob/exc.py index c27f6cd..b072b8c 100644 --- a/webob/exc.py +++ b/webob/exc.py @@ -209,7 +209,7 @@ class HTTPException(Exception): exception = property(exception) # for old style exceptions - if not newstyle_exceptions: + if not newstyle_exceptions: #pragma NO COVERAGE def __getattr__(self, attr): if not attr.startswith('_'): return getattr(self.wsgi_response, attr) @@ -1062,10 +1062,10 @@ class HTTPExceptionMiddleware(object): try: from paste import httpexceptions -except ImportError: +except ImportError: # pragma: no cover # Without Paste we don't need to do this fixup pass -else: +else: # pragma: no cover for name in dir(httpexceptions): obj = globals().get(name) if (obj and isinstance(obj, type) and issubclass(obj, HTTPException) diff --git a/webob/multidict.py b/webob/multidict.py index b32bc5a..e9d3914 100644 --- a/webob/multidict.py +++ b/webob/multidict.py @@ -3,9 +3,12 @@ """ Gives a multi-value dictionary object (MultiDict) plus several wrappers """ -import cgi, copy, sys, warnings, urllib -from UserDict import DictMixin +import cgi +import copy +import sys +import warnings +from UserDict import DictMixin __all__ = ['MultiDict', 'UnicodeMultiDict', 'NestedMultiDict', 'NoVars', @@ -20,7 +23,8 @@ class MultiDict(DictMixin): def __init__(self, *args, **kw): if len(args) > 1: - raise TypeError("MultiDict can only be called with one positional argument") + raise TypeError( + "MultiDict can only be called with one positional argument") if args: if hasattr(args[0], 'iteritems'): items = list(args[0].iteritems()) @@ -186,8 +190,8 @@ class MultiDict(DictMixin): if args: lst = args[0] if len(lst) != len(dict(lst)): - # this does not catch the cases where we overwrite existing keys, - # but those would produce too many warning + # this does not catch the cases where we overwrite existing + # keys, but those would produce too many warning msg = ("Behavior of MultiDict.update() has changed " "and overwrites duplicate keys. Consider using .extend()" ) @@ -326,7 +330,8 @@ class UnicodeMultiDict(DictMixin): """ Return a list of all values matching the key (may be an empty list) """ - return [self._decode_value(v) for v in self.multi.getall(self._encode_key(key))] + return [self._decode_value(v) for v in + self.multi.getall(self._encode_key(key))] def getone(self, key): """ @@ -378,7 +383,9 @@ class UnicodeMultiDict(DictMixin): return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors) def setdefault(self, key, default=None): - return self._decode_value(self.multi.setdefault(self._encode_key(key), self._encode_value(default))) + return self._decode_value( + self.multi.setdefault(self._encode_key(key), + self._encode_value(default))) def pop(self, key, *args): return self._decode_value(self.multi.pop(self._encode_key(key), *args)) diff --git a/webob/request.py b/webob/request.py index cede84e..f7442c0 100644 --- a/webob/request.py +++ b/webob/request.py @@ -1,26 +1,53 @@ -import sys, tempfile, warnings -import urllib, urlparse, cgi +import cgi +import re +import sys +import tempfile +import urllib +import urlparse if sys.version >= '2.7': - from io import BytesIO as StringIO + from io import BytesIO as StringIO # pragma nocover else: - from cStringIO import StringIO - -from webob.headers import EnvironHeaders -from webob.acceptparse import accept_property, Accept, MIMEAccept, NilAccept, MIMENilAccept, NoAccept -from webob.multidict import TrackableMultiDict, MultiDict, UnicodeMultiDict, NestedMultiDict, NoVars -from webob.cachecontrol import CacheControl, serialize_cache_control -from webob.etag import etag_property, AnyETag, NoETag - -from webob.descriptors import * -from webob.datetime_utils import * + from cStringIO import StringIO # pragma nocover + +from webob.acceptparse import MIMEAccept +from webob.acceptparse import MIMENilAccept +from webob.acceptparse import NoAccept +from webob.acceptparse import accept_property +from webob.cachecontrol import CacheControl +from webob.cachecontrol import serialize_cache_control from webob.cookies import Cookie +from webob.descriptors import CHARSET_RE +from webob.descriptors import SCHEME_RE +from webob.descriptors import converter +from webob.descriptors import converter_date +from webob.descriptors import deprecated_property +from webob.descriptors import environ_getter +from webob.descriptors import parse_auth +from webob.descriptors import parse_if_range +from webob.descriptors import parse_int +from webob.descriptors import parse_int_safe +from webob.descriptors import parse_range +from webob.descriptors import serialize_auth +from webob.descriptors import serialize_if_range +from webob.descriptors import serialize_int +from webob.descriptors import serialize_range +from webob.descriptors import upath_property +from webob.etag import AnyETag +from webob.etag import NoETag +from webob.etag import etag_property +from webob.headers import EnvironHeaders +from webob.multidict import MultiDict +from webob.multidict import NestedMultiDict +from webob.multidict import NoVars +from webob.multidict import TrackableMultiDict +from webob.multidict import UnicodeMultiDict __all__ = ['BaseRequest', 'Request'] if sys.version >= '2.6': parse_qsl = urlparse.parse_qsl else: - parse_qsl = cgi.parse_qsl + parse_qsl = cgi.parse_qsl # pragma nocover class _NoDefault: def __repr__(self): @@ -38,8 +65,8 @@ class BaseRequest(object): ## in memory): request_body_tempfile_limit = 10*1024 - def __init__(self, environ=None, environ_getter=None, charset=NoDefault, unicode_errors=NoDefault, - decode_param_names=NoDefault, **kw): + def __init__(self, environ=None, environ_getter=None, charset=NoDefault, + unicode_errors=NoDefault, decode_param_names=NoDefault, **kw): if environ_getter is not None: raise ValueError('The environ_getter argument is no longer ' 'supported') @@ -53,7 +80,8 @@ class BaseRequest(object): if (isinstance(getattr(cls, 'charset', None), str) or hasattr(cls, 'default_charset') ): - raise DeprecationWarning("The class attr [default_]charset is deprecated") + raise DeprecationWarning( + "The class attr [default_]charset is deprecated") if unicode_errors is not NoDefault: d['unicode_errors'] = unicode_errors if decode_param_names is not NoDefault: @@ -62,7 +90,8 @@ class BaseRequest(object): my_class = self.__class__ for name, value in kw.iteritems(): if not hasattr(my_class, name): - raise TypeError("Unexpected keyword: %s=%r" % (name, value)) + raise TypeError( + "Unexpected keyword: %s=%r" % (name, value)) setattr(self, name, value) @@ -83,14 +112,17 @@ class BaseRequest(object): self.is_body_seekable = False def _body_file__del(self): self.body = '' - body_file = property(_body_file__get, _body_file__set, _body_file__del, doc=_body_file__get.__doc__) + body_file = property(_body_file__get, + _body_file__set, + _body_file__del, + doc=_body_file__get.__doc__) body_file_raw = environ_getter('wsgi.input') @property def body_file_seekable(self): """ Get the body of the request (wsgi.input) as a seekable file-like - object. Middleware and routing applications should use this attribute - over .body_file. + object. Middleware and routing applications should use this + attribute over .body_file. If you access this value, CONTENT_LENGTH will also be updated. """ @@ -142,7 +174,9 @@ class BaseRequest(object): if 'CONTENT_TYPE' in self.environ: del self.environ['CONTENT_TYPE'] - content_type = property(_content_type__get, _content_type__set, _content_type__del, + content_type = property(_content_type__get, + _content_type__set, + _content_type__del, _content_type__get.__doc__) _charset_cache = (None, None) @@ -180,7 +214,8 @@ class BaseRequest(object): content_type = self.environ.get('CONTENT_TYPE', '') charset_match = CHARSET_RE.search(self.environ.get('CONTENT_TYPE', '')) if charset_match: - content_type = content_type[:charset_match.start(1)] + charset + content_type[charset_match.end(1):] + content_type = (content_type[:charset_match.start(1)] + + charset + content_type[charset_match.end(1):]) # comma to separate params? there's nothing like that in RFCs AFAICT #elif ';' in content_type: # content_type += ', charset="%s"' % charset @@ -188,7 +223,8 @@ class BaseRequest(object): content_type += '; charset="%s"' % charset self.environ['CONTENT_TYPE'] = content_type def _charset__del(self): - new_content_type = CHARSET_RE.sub('', self.environ.get('CONTENT_TYPE', '')) + new_content_type = CHARSET_RE.sub('', + self.environ.get('CONTENT_TYPE', '')) new_content_type = new_content_type.rstrip().rstrip(';').rstrip(',') self.environ['CONTENT_TYPE'] = new_content_type @@ -360,7 +396,8 @@ class BaseRequest(object): def _urlvars__set(self, value): environ = self.environ if 'wsgiorg.routing_args' in environ: - environ['wsgiorg.routing_args'] = (environ['wsgiorg.routing_args'][0], value) + environ['wsgiorg.routing_args'] = ( + environ['wsgiorg.routing_args'][0], value) if 'paste.urlvars' in environ: del environ['paste.urlvars'] elif 'paste.urlvars' in environ: @@ -375,9 +412,13 @@ class BaseRequest(object): if not self.environ['wsgiorg.routing_args'][0]: del self.environ['wsgiorg.routing_args'] else: - self.environ['wsgiorg.routing_args'] = (self.environ['wsgiorg.routing_args'][0], {}) + self.environ['wsgiorg.routing_args'] = ( + self.environ['wsgiorg.routing_args'][0], {}) - urlvars = property(_urlvars__get, _urlvars__set, _urlvars__del, doc=_urlvars__get.__doc__) + urlvars = property(_urlvars__get, + _urlvars__set, + _urlvars__del, + doc=_urlvars__get.__doc__) def _urlargs__get(self): """ @@ -410,19 +451,24 @@ class BaseRequest(object): if not self.environ['wsgiorg.routing_args'][1]: del self.environ['wsgiorg.routing_args'] else: - self.environ['wsgiorg.routing_args'] = ((), self.environ['wsgiorg.routing_args'][1]) + self.environ['wsgiorg.routing_args'] = ( + (), self.environ['wsgiorg.routing_args'][1]) - urlargs = property(_urlargs__get, _urlargs__set, _urlargs__del, _urlargs__get.__doc__) + urlargs = property(_urlargs__get, + _urlargs__set, + _urlargs__del, + _urlargs__get.__doc__) @property def is_xhr(self): - """Returns a boolean if X-Requested-With is present and ``XMLHttpRequest`` + """Is X-Requested-With header present and equal to ``XMLHttpRequest``? Note: this isn't set by every XMLHttpRequest request, it is only set if you are using a Javascript library that sets it (or you set the header yourself manually). Currently Prototype and jQuery are known to set this header.""" - return self.environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest' + return self.environ.get('HTTP_X_REQUESTED_WITH', '' + ) == 'XMLHttpRequest' def _host__get(self): """Host name provided in HTTP_HOST, with fall-back to SERVER_NAME""" @@ -449,7 +495,8 @@ class BaseRequest(object): if value is None: value = '' if not isinstance(value, str): - raise TypeError("You can only set Request.body to a str (not %r)" % type(value)) + raise TypeError("You can only set Request.body to a str (not %r)" + % type(value)) self.content_length = len(value) self.body_file_raw = StringIO(value) self.is_body_seekable = True @@ -478,7 +525,8 @@ class BaseRequest(object): content_type = self.content_type if ((self.method == 'PUT' and not content_type) or content_type not in - ('', 'application/x-www-form-urlencoded', 'multipart/form-data') + ('', 'application/x-www-form-urlencoded', + 'multipart/form-data') ): # Not an HTML form submission return NoVars('Not an HTML form submission (Content-Type: %s)' @@ -530,9 +578,10 @@ class BaseRequest(object): if not source: vars = TrackableMultiDict(__tracker=self._update_get, __name='GET') else: - vars = TrackableMultiDict(parse_qsl( - source, keep_blank_values=True, - strict_parsing=False), __tracker=self._update_get, __name='GET') + vars = TrackableMultiDict(parse_qsl(source, + keep_blank_values=True, + strict_parsing=False), + __tracker=self._update_get, __name='GET') env['webob._parsed_query_vars'] = (vars, source) return vars @@ -555,9 +604,11 @@ class BaseRequest(object): return vars - str_postvars = deprecated_property(str_POST, 'str_postvars', 'use str_POST instead') + str_postvars = deprecated_property(str_POST, 'str_postvars', + 'use str_POST instead') postvars = deprecated_property(POST, 'postvars', 'use POST instead') - str_queryvars = deprecated_property(str_GET, 'str_queryvars', 'use str_GET instead') + str_queryvars = deprecated_property(str_GET, 'str_queryvars', + 'use str_GET instead') queryvars = deprecated_property(GET, 'queryvars', 'use GET instead') @@ -597,10 +648,7 @@ class BaseRequest(object): if source: cookies = Cookie(source) for name in cookies: - value = cookies[name].value - if value is None: - continue - vars[name] = value + vars[name] = cookies[name].value env['webob._parsed_cookies'] = (vars, source) return vars @@ -631,8 +679,8 @@ class BaseRequest(object): def copy_get(self): """ Copies the request and environment object, but turning this request - into a GET along the way. If this was a POST request (or any other verb) - then it becomes GET, and the request body is thrown away. + into a GET along the way. If this was a POST request (or any other + verb) then it becomes GET, and the request body is thrown away. """ env = self.environ.copy() return self.__class__(env, method='GET', content_type=None, body='') @@ -642,10 +690,12 @@ class BaseRequest(object): def make_body_seekable(self): """ This forces ``environ['wsgi.input']`` to be seekable. - That means that, the content is copied into a StringIO or temporary file - and flagged as seekable, so that it will not be unnecessarily copied again. - After calling this method the .body_file is always seeked to the start of file - and .content_length is not None. + That means that, the content is copied into a StringIO or temporary + file and flagged as seekable, so that it will not be unnecessarily + copied again. + + After calling this method the .body_file is always seeked to the + start of file and .content_length is not None. The choice to copy to StringIO is made from ``self.request_body_tempfile_limit`` @@ -701,8 +751,11 @@ class BaseRequest(object): return tempfile.TemporaryFile() - def remove_conditional_headers(self, remove_encoding=True, remove_range=True, - remove_match=True, remove_modified=True): + def remove_conditional_headers(self, + remove_encoding=True, + remove_range=True, + remove_match=True, + remove_modified=True): """ Remove headers that make the request conditional. @@ -727,9 +780,11 @@ class BaseRequest(object): del self.environ[key] - accept = accept_property('Accept', '14.1', MIMEAccept, MIMENilAccept, 'MIME Accept') + accept = accept_property('Accept', '14.1', + MIMEAccept, MIMENilAccept, 'MIME Accept') accept_charset = accept_property('Accept-Charset', '14.2') - accept_encoding = accept_property('Accept-Encoding', '14.3', NilClass=NoAccept) + accept_encoding = accept_property('Accept-Encoding', '14.3', + NilClass=NoAccept) accept_language = accept_property('Accept-Language', '14.4') authorization = converter( @@ -748,7 +803,9 @@ class BaseRequest(object): cache_header, cache_obj = env.get('webob._cache_control', (None, None)) if cache_obj is not None and cache_header == value: return cache_obj - cache_obj = CacheControl.parse(value, updates_to=self._update_cache_control, type='request') + cache_obj = CacheControl.parse(value, + updates_to=self._update_cache_control, + type='request') env['webob._cache_control'] = (value, cache_obj) return cache_obj @@ -775,15 +832,20 @@ class BaseRequest(object): def _update_cache_control(self, prop_dict): self.environ['HTTP_CACHE_CONTROL'] = serialize_cache_control(prop_dict) - cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__) + cache_control = property(_cache_control__get, + _cache_control__set, + _cache_control__del, + doc=_cache_control__get.__doc__) if_match = etag_property('HTTP_IF_MATCH', AnyETag, '14.24') if_none_match = etag_property('HTTP_IF_NONE_MATCH', NoETag, '14.26') date = converter_date(environ_getter('HTTP_DATE', None, '14.8')) - if_modified_since = converter_date(environ_getter('HTTP_IF_MODIFIED_SINCE', None, '14.25')) - if_unmodified_since = converter_date(environ_getter('HTTP_IF_UNMODIFIED_SINCE', None, '14.28')) + if_modified_since = converter_date( + environ_getter('HTTP_IF_MODIFIED_SINCE', None, '14.25')) + if_unmodified_since = converter_date( + environ_getter('HTTP_IF_UNMODIFIED_SINCE', None, '14.28')) if_range = converter( environ_getter('HTTP_IF_RANGE', None, '14.27'), parse_if_range, serialize_if_range, 'IfRange object') @@ -873,7 +935,8 @@ class BaseRequest(object): not read every valid HTTP request properly.""" start_line = fp.readline() try: - method, resource, http_version = start_line.rstrip('\r\n').split(None, 2) + method, resource, http_version = start_line.rstrip('\r\n' + ).split(None, 2) except ValueError: raise ValueError('Bad HTTP request line: %r' % start_line) r = cls(environ_from_url(resource), @@ -957,7 +1020,8 @@ class BaseRequest(object): request=self) @classmethod - def blank(cls, path, environ=None, base_url=None, headers=None, POST=None, **kw): + def blank(cls, path, environ=None, base_url=None, + headers=None, POST=None, **kw): """ Create a blank request environ (and Request wrapper) with the given path (path should be urlencoded), and any keys from @@ -1066,23 +1130,19 @@ def environ_add_POST(env, data): class AdhocAttrMixin(object): def __setattr__(self, attr, value, DEFAULT=object()): - ## FIXME: I don't know why I need this guard (though experimentation says I do) - if getattr(self.__class__, attr, DEFAULT) is not DEFAULT or attr.startswith('_'): + if (getattr(self.__class__, attr, DEFAULT) is not DEFAULT or + attr.startswith('_')): object.__setattr__(self, attr, value) else: self.environ.setdefault('webob.adhoc_attrs', {})[attr] = value def __getattr__(self, attr, DEFAULT=object()): - ## FIXME: I don't know why I need this guard (though experimentation says I do) - if getattr(self.__class__, attr, DEFAULT) is not DEFAULT: - return object.__getattribute__(self, attr) try: return self.environ['webob.adhoc_attrs'][attr] except KeyError: raise AttributeError(attr) def __delattr__(self, attr, DEFAULT=object()): - ## FIXME: I don't know why I need this guard (though experimentation says I do) if getattr(self.__class__, attr, DEFAULT) is not DEFAULT: return object.__delattr__(self, attr) try: @@ -1146,7 +1206,8 @@ class FakeCGIBody(object): def _get_body(self): if self._body is None: - if self.content_type.lower().startswith('application/x-www-form-urlencoded'): + if self.content_type.lower().startswith( + 'application/x-www-form-urlencoded'): self._body = urllib.urlencode(self.vars.items()) elif self.content_type.lower().startswith('multipart/form-data'): self._body = _encode_multipart(self.vars, self.content_type) @@ -1196,7 +1257,8 @@ def _encode_multipart(vars, content_type): """Encode a multipart request body into a string""" boundary_match = re.search(r'boundary=([^ ]+)', content_type, re.I) if not boundary_match: - raise ValueError('Content-type: %r does not contain boundary' % content_type) + raise ValueError('Content-type: %r does not contain boundary' + % content_type) boundary = boundary_match.group(1).strip('"') lines = [] for name, value in vars.iteritems(): @@ -1212,7 +1274,8 @@ def _encode_multipart(vars, content_type): ct = 'Content-type: %s' % value.type if value.type_options: ct += ''.join(['; %s="%s"' % (ct_name, ct_value) - for ct_name, ct_value in sorted(value.type_options.items())]) + for ct_name, ct_value in sorted( + value.type_options.items())]) lines.append(ct) lines.append('') if hasattr(value, 'value'): diff --git a/webob/response.py b/webob/response.py index 37fa39f..9aba71e 100644 --- a/webob/response.py +++ b/webob/response.py @@ -1,11 +1,38 @@ -import sys, re, urlparse, zlib, struct -from datetime import datetime, date, timedelta +import re +import urlparse +import zlib +import struct + +from datetime import datetime +from datetime import timedelta from webob.headers import ResponseHeaders -from webob.cachecontrol import CacheControl, serialize_cache_control +from webob.cachecontrol import CacheControl +from webob.cachecontrol import serialize_cache_control + +from webob.byterange import ContentRange + +from webob.descriptors import deprecated_property +from webob.descriptors import list_header +from webob.descriptors import converter +from webob.descriptors import header_getter +from webob.descriptors import parse_int +from webob.descriptors import serialize_int +from webob.descriptors import parse_content_range +from webob.descriptors import serialize_content_range +from webob.descriptors import date_header +from webob.descriptors import parse_etag_response +from webob.descriptors import serialize_etag_response +from webob.descriptors import parse_int_safe +from webob.descriptors import parse_auth +from webob.descriptors import serialize_auth +from webob.descriptors import CHARSET_RE +from webob.descriptors import SCHEME_RE + +from webob.datetime_utils import parse_date_delta +from webob.datetime_utils import serialize_date_delta +from webob.datetime_utils import timedelta_to_seconds -from webob.descriptors import * -from webob.datetime_utils import * from webob.cookies import Cookie, Morsel from webob.util import status_reasons from webob.request import StringIO @@ -89,7 +116,8 @@ class Response(object): if isinstance(body, unicode): if charset is None: raise TypeError( - "You cannot set the body to a unicode value without a charset") + "You cannot set the body to a unicode value without a " + "charset") body = body.encode(charset) self._body = body if headerlist is None: @@ -118,7 +146,6 @@ class Response(object): must have a ``Content-Length``""" headerlist = [] status = fp.readline().strip() - content_length = None while 1: line = fp.readline().strip() if not line: @@ -157,7 +184,8 @@ class Response(object): # def __repr__(self): - return '<%s at 0x%x %s>' % (self.__class__.__name__, abs(id(self)), self.status) + return '<%s at 0x%x %s>' % (self.__class__.__name__, abs(id(self)), + self.status) def __str__(self, skip_body=False): parts = [self.status] @@ -206,7 +234,8 @@ class Response(object): return int(self._status.split()[0]) def _status_int__set(self, code): self._status = '%d %s' % (code, status_reasons[code]) - status_int = property(_status_int__get, _status_int__set, doc=_status_int__get.__doc__) + status_int = property(_status_int__get, _status_int__set, + doc=_status_int__get.__doc__) status_code = deprecated_property( status_int, 'status_code', 'use .status or .status_int instead', @@ -234,7 +263,8 @@ class Response(object): def _headerlist__del(self): self.headerlist = [] - headerlist = property(_headerlist__get, _headerlist__set, _headerlist__del, doc=_headerlist__get.__doc__) + headerlist = property(_headerlist__get, _headerlist__set, + _headerlist__del, doc=_headerlist__get.__doc__) def _headers__get(self): """ @@ -275,19 +305,22 @@ class Response(object): app_iter_repr = ( app_iter_repr[:30] + '...' + app_iter_repr[-10:]) raise ValueError( - 'An item of the app_iter (%s) was unicode, causing a unicode body: %r' - % (app_iter_repr, body)) + 'An item of the app_iter (%s) was unicode, causing a ' + 'unicode body: %r' % (app_iter_repr, body)) self._app_iter = None - if self._environ is not None and self._environ['REQUEST_METHOD'] == 'HEAD': + if (self._environ is not None and + self._environ['REQUEST_METHOD'] == 'HEAD'): assert len(body) == 0, "HEAD responses must be empty" elif len(body) == 0: - # if body-length is zero, we assume it's a HEAD response and leave content_length alone - pass + # if body-length is zero, we assume it's a HEAD response and + # leave content_length alone + pass # pragma: no cover (no idea why necessary, it's hit) elif self.content_length is None: self.content_length = len(body) elif self.content_length != len(body): raise AssertionError( - "Content-Length is different from actual app_iter length (%r!=%r)" + "Content-Length is different from actual app_iter length " + "(%r!=%r)" % (self.content_length, len(body)) ) return self._body @@ -295,7 +328,8 @@ class Response(object): def _body__set(self, value): if isinstance(value, unicode): raise TypeError( - "You cannot set Response.body to a unicode object (use Response.unicode_body)") + "You cannot set Response.body to a unicode object (use " + "Response.unicode_body)") if not isinstance(value, str): raise TypeError( "You can only set the body to a str (not %s)" @@ -304,7 +338,8 @@ class Response(object): if self._body or self._app_iter: self.content_md5 = None except AttributeError: - # if setting body early in initialization _body and _app_iter don't exist yet + # if setting body early in initialization _body and _app_iter + # don't exist yet pass self._body = value self.content_length = len(value) @@ -324,7 +359,8 @@ class Response(object): def _unicode_body__get(self): """ - Get/set the unicode value of the body (using the charset of the Content-Type) + Get/set the unicode value of the body (using the charset of the + Content-Type) """ if not self.charset: raise AttributeError( @@ -338,14 +374,17 @@ class Response(object): "You cannot access Response.unicode_body unless charset is set") if not isinstance(value, unicode): raise TypeError( - "You can only set Response.unicode_body to a unicode string (not %s)" % type(value)) + "You can only set Response.unicode_body to a unicode string " + "(not %s)" % type(value)) self.body = value.encode(self.charset) def _unicode_body__del(self): del self.body - unicode_body = property(_unicode_body__get, _unicode_body__set, _unicode_body__del, doc=_unicode_body__get.__doc__) - ubody = property(_unicode_body__get, _unicode_body__set, _unicode_body__del, doc="Alias for unicode_body") + unicode_body = property(_unicode_body__get, _unicode_body__set, + _unicode_body__del, doc=_unicode_body__get.__doc__) + ubody = property(_unicode_body__get, _unicode_body__set, + _unicode_body__del, doc="Alias for unicode_body") @@ -364,7 +403,8 @@ class Response(object): def _body_file__del(self): del self.body - body_file = property(_body_file__get, fdel=_body_file__del, doc=_body_file__get.__doc__) + body_file = property(_body_file__get, fdel=_body_file__del, + doc=_body_file__get.__doc__) def write(self, text): if isinstance(text, unicode): @@ -402,7 +442,8 @@ class Response(object): self.content_length = None self._app_iter = self._body = None - app_iter = property(_app_iter__get, _app_iter__set, _app_iter__del, doc=_app_iter__get.__doc__) + app_iter = property(_app_iter__get, _app_iter__set, _app_iter__del, + doc=_app_iter__get.__doc__) @@ -479,7 +520,8 @@ class Response(object): return header = self.headers.pop('Content-Type', None) if header is None: - raise AttributeError("You cannot set the charset when no content-type is defined") + raise AttributeError("You cannot set the charset when no " + "content-type is defined") match = CHARSET_RE.search(header) if match: header = header[:match.start()] + header[match.end():] @@ -496,7 +538,8 @@ class Response(object): header = header[:match.start()] + header[match.end():] self.headers['Content-Type'] = header - charset = property(_charset__get, _charset__set, _charset__del, doc=_charset__get.__doc__) + charset = property(_charset__get, _charset__set, _charset__del, + doc=_charset__get.__doc__) # @@ -569,7 +612,8 @@ class Response(object): self.headers['Content-Type'] = ct def _content_type_params__del(self): - self.headers['Content-Type'] = self.headers.get('Content-Type', '').split(';', 1)[0] + self.headers['Content-Type'] = self.headers.get( + 'Content-Type', '').split(';', 1)[0] content_type_params = property( _content_type_params__get, @@ -660,10 +704,12 @@ class Response(object): resp.headers.add('Set-Cookie', header) return resp else: - c_headers = [h for h in self.headerlist if h[0].lower() == 'set-cookie'] + c_headers = [h for h in self.headerlist if + h[0].lower() == 'set-cookie'] def repl_app(environ, start_response): def repl_start_response(status, headers, exc_info=None): - return start_response(status, headers+c_headers, exc_info=exc_info) + return start_response(status, headers+c_headers, + exc_info=exc_info) return resp(environ, repl_start_response) return repl_app @@ -681,7 +727,8 @@ class Response(object): """ value = self.headers.get('cache-control', '') if self._cache_control_obj is None: - self._cache_control_obj = CacheControl.parse(value, updates_to=self._update_cache_control, type='response') + self._cache_control_obj = CacheControl.parse( + value, updates_to=self._update_cache_control, type='response') self._cache_control_obj.header_value = value if self._cache_control_obj.header_value != value: new_obj = CacheControl.parse(value, type='response') @@ -718,7 +765,9 @@ class Response(object): else: self.headers['Cache-Control'] = value - cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__) + cache_control = property( + _cache_control__get, _cache_control__set, + _cache_control__del, doc=_cache_control__get.__doc__) # @@ -772,7 +821,8 @@ class Response(object): Encode the content with the given encoding (only gzip and identity are supported). """ - assert encoding in ('identity', 'gzip'), "Unknown encoding: %r" % encoding + assert encoding in ('identity', 'gzip'), \ + "Unknown encoding: %r" % encoding if encoding == 'identity': self.decode_content() return @@ -802,7 +852,6 @@ class Response(object): gzip_f.close() f.close() else: - import zlib # Weird feature: http://bugs.python.org/issue5784 self.body = zlib.decompress(self.body, -15) self.content_encoding = None @@ -817,9 +866,9 @@ class Response(object): """ if body is None: body = self.body - try: + try: # pragma: no cover from hashlib import md5 - except ImportError: + except ImportError: # pragma: no cover from md5 import md5 md5_digest = md5(body).digest().encode('base64').replace('\n', '') self.etag = md5_digest.strip('=') @@ -853,7 +902,8 @@ class Response(object): def _request__del(self): self._request = self._environ = None - request = property(_request__get, _request__set, _request__del, doc=_request__get.__doc__) + request = property(_request__get, _request__set, _request__del, + doc=_request__get.__doc__) # @@ -876,7 +926,8 @@ class Response(object): def _environ__del(self): self._request = self._environ = None - environ = property(_environ__get, _environ__set, _environ__del, doc=_environ__get.__doc__) + environ = property(_environ__get, _environ__set, _environ__del, + doc=_environ__get.__doc__) @@ -909,7 +960,8 @@ class Response(object): new_location = urlparse.urljoin( _request_uri(environ), value) headerlist = list(headerlist) - headerlist[headerlist.index((name, value))] = (name, new_location) + idx = headerlist.index((name, value)) + headerlist[idx] = (name, new_location) break return headerlist @@ -946,21 +998,29 @@ class Response(object): iter_close(self.app_iter) body = "Requested range not satisfiable: %s" % req.range headerlist = [ - ('Content-Length', str(len(body))), - ('Content-Range', str(ContentRange(None, None, self.content_length))), + ('Content-Length', + str(len(body))), + ('Content-Range', + str(ContentRange(None, None, self.content_length))), ('Content-Type', 'text/plain'), ] + filter_headers(headerlist) - start_response('416 Requested Range Not Satisfiable', headerlist) + start_response('416 Requested Range Not Satisfiable', + headerlist) if req.method == 'HEAD': return () return [body] else: - app_iter = self.app_iter_range(content_range.start, content_range.stop) + app_iter = self.app_iter_range(content_range.start, + content_range.stop) if app_iter is not None: - assert content_range.start is not None # this should be guaranteed by Range.range_for_length(length) + assert content_range.start is not None + # above should be guaranteed by + # Range.range_for_length(length) headerlist = [ - ('Content-Length', str(content_range.stop - content_range.start)), - ('Content-Range', str(content_range)), + ('Content-Length', + str(content_range.stop - content_range.start)), + ('Content-Range', + str(content_range)), ] + filter_headers(headerlist, ('content-length',)) start_response('206 Partial Content', headerlist) if req.method == 'HEAD': @@ -1145,7 +1205,8 @@ def iter_close(iter): def gzip_app_iter(app_iter): size = 0 crc = zlib.crc32("") & 0xffffffffL - compress = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) + compress = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS, + zlib.DEF_MEM_LEVEL, 0) yield _gzip_header for item in app_iter: |