summaryrefslogtreecommitdiff
path: root/webob
diff options
context:
space:
mode:
Diffstat (limited to 'webob')
-rw-r--r--webob/cachecontrol.py42
-rw-r--r--webob/cookies.py25
-rw-r--r--webob/datetime_utils.py10
-rw-r--r--webob/dec.py10
-rw-r--r--webob/descriptors.py9
-rw-r--r--webob/exc.py6
-rw-r--r--webob/multidict.py21
-rw-r--r--webob/request.py197
-rw-r--r--webob/response.py153
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: