summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-03-20 23:25:37 -0400
committerChris McDonough <chrism@plope.com>2011-03-20 23:25:37 -0400
commitd8176e7279e19ab3e618056555a05d710bb161d4 (patch)
tree43717841fd1326d22a4aaea2d1a17c1c0ce72fcb
parent1c4912af221e89ecf117154e2708f58984287ad8 (diff)
parentc3f021fbf2fd027b0778ca9dadc79ae7b2a5394b (diff)
downloadwebob-tests.pycon2011.tar.gz
merge from headtests.pycon2011
-rw-r--r--.hgignore5
-rw-r--r--_test_mlk.py30
-rw-r--r--docs/doctests.py17
-rw-r--r--docs/file-example.txt2
-rw-r--r--docs/index.txt2
-rw-r--r--docs/reference.txt2
-rw-r--r--docs/test_dec.txt (renamed from tests/test_dec.txt)2
-rw-r--r--docs/test_request.txt (renamed from tests/test_request.txt)2
-rw-r--r--docs/test_response.txt (renamed from tests/test_response.txt)2
-rw-r--r--pycon-py3k-sprint.txt75
-rw-r--r--request_table.rst145
-rw-r--r--response_table.rst68
-rw-r--r--setup.cfg9
-rw-r--r--setup.py4
-rwxr-xr-xtest8
-rw-r--r--tests/performance_test.py3
-rw-r--r--tests/test_acceptparse.py387
-rw-r--r--tests/test_byterange.py231
-rw-r--r--tests/test_cachecontrol.py266
-rw-r--r--tests/test_cookies.py85
-rw-r--r--tests/test_datetime_utils.py61
-rw-r--r--tests/test_dec.py267
-rw-r--r--tests/test_descriptors.py797
-rw-r--r--tests/test_etag.py451
-rw-r--r--tests/test_exc.py342
-rw-r--r--tests/test_headers.py100
-rw-r--r--tests/test_multidict.py330
-rw-r--r--tests/test_request.py3081
-rw-r--r--tests/test_response.py820
-rw-r--r--tox.ini39
-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
39 files changed, 7128 insertions, 978 deletions
diff --git a/.hgignore b/.hgignore
index 3cd6593..faf8176 100644
--- a/.hgignore
+++ b/.hgignore
@@ -4,6 +4,7 @@ WebOb.egg-info
*.pyc
*.pyo
*.swp
+*~
TEST*
testenv
docs/_build/*
@@ -16,4 +17,8 @@ Session.vim
html_coverage/**
*,cover
*_fixt.py
+*$py.class
+.tox/
+coverage.xml
+nosetests.xml
diff --git a/_test_mlk.py b/_test_mlk.py
deleted file mode 100644
index 007bf92..0000000
--- a/_test_mlk.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import sys, site, os, tempfile
-
-testenv_dir = os.path.join(tempfile.gettempdir(), 'webob-testenv')
-if not os.path.exists(testenv_dir):
- os.makedirs(testenv_dir)
- from setuptools.command.easy_install import main
- site.USER_SITE = testenv_dir
- libs = 'nose dtopt webtest mext.test>=0.4.2 coverage'.split()
- main(['-x', '-N', '-d', testenv_dir] + libs)
-
-site.addsitedir(testenv_dir)
-
-from mext.test_suite import TestSuite
-suite = TestSuite('tests', coverage=True, pkg='webob')
-
-doctests = ['test_dec', 'test_request', 'test_response']
-doctests += map('../docs/'.__add__, ['do-it-yourself', 'file-example', 'index', 'reference'])
-map(suite.add_doctest, doctests)
-suite.add_unittest('test_multidict')
-map(suite.add_nosetest, [
- 'test_request', 'test_response', 'test_headers',
- 'test_cookies', 'test_exc',
- 'test_misc', 'test_datetime_utils',
-])
-
-
-if __name__ == '__main__':
- suite.run_text()
-
-
diff --git a/docs/doctests.py b/docs/doctests.py
new file mode 100644
index 0000000..9aecb31
--- /dev/null
+++ b/docs/doctests.py
@@ -0,0 +1,17 @@
+import unittest
+import doctest
+
+def test_suite():
+ flags = doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE
+ return unittest.TestSuite((
+ doctest.DocFileSuite('test_request.txt', optionflags=flags),
+ doctest.DocFileSuite('test_response.txt', optionflags=flags),
+ doctest.DocFileSuite('test_dec.txt', optionflags=flags),
+ doctest.DocFileSuite('do-it-yourself.txt', optionflags=flags),
+ doctest.DocFileSuite('file-example.txt', optionflags=flags),
+ doctest.DocFileSuite('index.txt', optionflags=flags),
+ doctest.DocFileSuite('reference.txt', optionflags=flags),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/docs/file-example.txt b/docs/file-example.txt
index 601e947..16d4d19 100644
--- a/docs/file-example.txt
+++ b/docs/file-example.txt
@@ -10,7 +10,7 @@ to a high-quality file serving application.
>>> import webob, os
>>> base_dir = os.path.dirname(os.path.dirname(webob.__file__))
>>> doc_dir = os.path.join(base_dir, 'docs')
- >>> from dtopt import ELLIPSIS
+ >>> from doctest import ELLIPSIS
First we'll setup a really simple shim around our application, which
we can use as we improve our application:
diff --git a/docs/index.txt b/docs/index.txt
index bad7be6..2e5c898 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -19,7 +19,7 @@ WebOb
.. comment:
- >>> from dtopt import ELLIPSIS
+ >>> from doctest import ELLIPSIS
Status & License
diff --git a/docs/reference.txt b/docs/reference.txt
index 188693b..b12ec40 100644
--- a/docs/reference.txt
+++ b/docs/reference.txt
@@ -5,7 +5,7 @@ WebOb Reference
.. comment:
- >>> from dtopt import ELLIPSIS
+ >>> from doctest import ELLIPSIS
Introduction
============
diff --git a/tests/test_dec.txt b/docs/test_dec.txt
index 40a0fc8..df0461c 100644
--- a/tests/test_dec.txt
+++ b/docs/test_dec.txt
@@ -1,6 +1,6 @@
A test of the decorator module::
- >>> from dtopt import ELLIPSIS
+ >>> from doctest import ELLIPSIS
>>> from webob.dec import wsgify
>>> from webob import Response, Request
>>> from webob import exc
diff --git a/tests/test_request.txt b/docs/test_request.txt
index 026b85c..66d26dd 100644
--- a/tests/test_request.txt
+++ b/docs/test_request.txt
@@ -16,7 +16,7 @@ should have.
... from io import BytesIO as InputType
... else:
... from cStringIO import InputType
- >>> from dtopt import ELLIPSIS, NORMALIZE_WHITESPACE
+ >>> from doctest import ELLIPSIS, NORMALIZE_WHITESPACE
>>> from webob import Request, UTC
>>> req = Request.blank('/')
>>> req # doctest: +ELLIPSIS
diff --git a/tests/test_response.txt b/docs/test_response.txt
index 655328d..9a5b47d 100644
--- a/tests/test_response.txt
+++ b/docs/test_response.txt
@@ -1,7 +1,7 @@
This demonstrates how the Response object works, and tests it at the
same time.
- >>> from dtopt import ELLIPSIS
+ >>> from doctest import ELLIPSIS
>>> from webob import Response, UTC
>>> from datetime import datetime
>>> res = Response('Test', status='200 OK')
diff --git a/pycon-py3k-sprint.txt b/pycon-py3k-sprint.txt
new file mode 100644
index 0000000..d5e7182
--- /dev/null
+++ b/pycon-py3k-sprint.txt
@@ -0,0 +1,75 @@
+Python 3 Sprint Outcomes
+========================
+
+We provided WebOb with 100% statement coverage at the 2011 PyCon Pyramid
+sprint in Atlanta GA.
+
+Participated:
+
+Alexandre Conrad, Patricio Paez, Whit Morriss, Rob Miller, Reed O'Brien,
+Chris Shenton, Joe Dallago, Tres Seaver, Casey Duncan, Kai Groner, Chris
+McDonough.
+
+In doing so, we added roughly 700-800 unit tests, and disused existing
+doctests as coverage (they are still runnable, but don't get run during
+``setup.py test``).
+
+We never did get around to actually doing any porting to Python 3. Adding
+comprehensive test coverage proved to be enough work to fill the sprint days.
+
+The bitbucket fork on which this work was done is at
+https://bitbucket.org/chrism/webob-py3k. I've made a tag in that repository
+named "sprint-coverage" which represents a reasonable place to pull from for
+integration into mainline.
+
+Testing Normally
+----------------
+
+ $ python2.x setup.py test
+
+Testing Coverage
+----------------
+
+ $ python2.X setup.py nosetests --with-coverage
+
+Testing Documentation
+---------------------
+
+Doctests don't run when you run "setup.py test" anymore. To run them
+manually, do:
+
+ $ cd webob
+ $ $MYVENV/bin/python setup.py develop
+ $ cd docs
+ $ $MYVENV/bin/python doctests.py
+
+Blamelist
+---------
+
+- webob.acceptparse (aconrad)
+
+- webob.byterange (ppaez)
+
+- webob.cachecontrol (whit)
+
+- webob.dec (rafrombrc)
+
+- webob.descriptors (reedobrien)
+
+- webob.etag (shentonfreude)
+
+- webob.multidict (joe)
+
+- webob.request (tseaver)
+
+- webob.response (caseman/mcdonc)
+
+- webob.exc (joe)
+
+Doctest-to-Unit Test Conversion
+-------------------------------
+
+- tests/test_request.txt (aconrad)
+
+- tests/test_response.txt (groner)
+
diff --git a/request_table.rst b/request_table.rst
new file mode 100644
index 0000000..927f031
--- /dev/null
+++ b/request_table.rst
@@ -0,0 +1,145 @@
+==========================
+ Request Comparison Table
+==========================
+
+b=WebBob
+z=Werkzeug
+x=both
+
+
+WEBOB NAME write read WERKZEUG NAME NOTES
+================================= ===== ==== ================================= ===========================================
+
+Read-Write Properties Read-Write Properties
++++++++++++++++++++++ +++++++++++++++++++++
+
+content_type content_type CommonRequestDescriptorMixin
+charset charset "utf-8"
+headers headers cached_property
+urlvars
+urlargs
+host host cached_property
+body
+unicode_errors 'strict' encoding_errors 'ignore'
+decode_param_names F
+request_body_tempfile_limit 10*1024 max_content_length None Not sure if these are the same
+ is_behind_proxy F
+ max_form_memory_size None
+ parameter_storage_class ImmutableMultiDict
+ list_storage_class ImmutableList
+ dict_storage_class ImmutableTypeConversionDict
+environ environ
+ populate_request T
+ shallow F
+
+
+Environ Getter Properties
++++++++++++++++++++++++++
+
+body_file_raw
+scheme
+method method
+http_version
+script_name script_root cached_property
+path_info ???path cached_property
+content_length content_type CommonRequestDescriptorMixin
+remote_user remote_user
+remote_addr remote_addr
+query_string query_string
+server_name host (with port)
+server_port host (with name)
+uscript_name
+upath_info
+is_body_seekable
+authorization authorization cached_property
+pragma pragma cached_property
+date date CommonRequestDescriptorMixin
+max_forwards max_forwards CommonRequestDescriptorMixin
+range
+if_range
+referer/referrer referrer CommonRequestDescriptorMixin
+user_agent user_agent cached_property
+ input_stream
+ mimetype CommonRequestDescriptorMixin
+
+
+Read-Only Properties
+++++++++++++++++++++
+
+host_url host_url cached_property
+application_url base_url cached_property Not sure if same
+path_url ???path cached_property
+path ???path cached_property
+path_qs ???path cached_property
+url url cached_property
+is_xhr is_xhr
+str_POST
+POST
+str_GET
+GET
+str_params
+params
+str_cookies
+cookies cookies cached_property
+ url_charset
+ stream cached_property
+ args cached_property Maybe maps to params
+ data cached_property
+ form cached_property
+ values cached_property Maybe maps to params
+ files cached_property
+ url_root cached_property
+ access_route cached_property
+ is_secure
+ is_multithread
+ is_multiprocess
+ is_run_once
+
+
+Accept Properties
++++++++++++++++++
+
+accept accept_mimetypes
+accept_charset accept_charsets
+accept_encoding accept_encodings
+accept_language accept_languages
+
+Etag Properties
++++++++++++++++
+
+cache_control cache_control cached_property
+if_match if_match cached_property
+if_none_match if_none_match cached_property
+if_modified_since if_modified_since cached_property
+if_unmodified_since if_unmodified_since cached_property
+
+Methods
+++++++
+
+relative_url
+path_info_pop
+path_info_peek
+copy
+copy_get
+make_body_seekable
+copy_body
+make_tempfile
+remove_conditional_headers
+as_string (__str__)
+call_application
+get_response
+
+Classmethods
+++++++++++++
+
+from_string (classmethod)
+from_file
+blank
+ from_values
+ application
+
+Notes
+-----
+
+ <mitsuhiko> mcdonc: script_root and path in werkzeug are not quite script_name and path_info in webob
+[17:51] <mitsuhiko> the behavior regarding slashes is different for easier url joining
diff --git a/response_table.rst b/response_table.rst
new file mode 100644
index 0000000..8f98ab1
--- /dev/null
+++ b/response_table.rst
@@ -0,0 +1,68 @@
+===========================
+ Response Comparison Table
+===========================
+
+b=WebBob
+z=Werkzeug
+x=both
+ =neither
+
+WEBOB NAME write read WERKZEUG NAME NOTES
+================================= ===== ==== ================================= ===========================================
+default_content_type x x default_mimetype wb default: "text/html", wz: "text/plain"
+default_charset b b wz uses class var default for charset
+charset x x charset
+unicode_errors b b
+default_conditional_response b b
+from_file() (classmethod) b b
+copy b b
+status (string) x x status
+status_int x x status_code
+ z default_status
+headers b b
+body b b
+unicode_body x x data
+body_file b File-like obj returned is writeable
+app_iter b x get_app_iter()
+ z iter_encoded()
+allow b x allow
+vary b x vary
+content_type x x content_type
+content_type_params x x mime_type_params
+ z z mime_type content_type str wo parameters
+content_length x x content_length
+content_encoding x x content_encoding
+content_language b x content_language
+content_location x x content_location
+content_md5 x x content_md5
+content_disposition b b
+accept_ranges b b
+content_range b b
+date x x date
+expires x x expires
+last_modified x x last_modified
+cache_control b z cache_control
+cache_expires (dwim) b b
+conditional_response (bool) b x make_conditional()
+etag b x add_etag()
+etag b x get_etag()
+etag b x set_etag()
+ z freeze()
+location x x location
+pragma b b
+age x x age
+retry_after x x retry_after
+server b b
+www_authenticate b z www_authenticate
+ x x date
+retry_after x x retry_after
+set_cookie() set_cookie()
+delete_cookie() delete_cookie()
+unset_cookie()
+ z is_streamed
+ z is_sequence
+body_file x stream
+ close()
+ get_wsgi_headers()
+ get_wsgi_response()
+__call__() __call__()
diff --git a/setup.cfg b/setup.cfg
index 1a9e54c..63e78c8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,13 +3,6 @@ distribute = register sdist upload
[nosetests]
detailed-errors = True
-with-doctest = True
-doctest-extension = txt
-doctest-fixtures= _fixt.py
-include = docs
-exclude = jsonrpc-example.txt
-with-coverage=True
cover-erase=True
-cover-html=True
-cover-html-dir=html_coverage
cover-package=webob
+nocapture=True
diff --git a/setup.py b/setup.py
index 63c708d..5ae12f9 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,4 @@
from setuptools import setup
-import sys, os
version = '1.0.5'
@@ -44,6 +43,5 @@ See also: `changelog <http://pythonpaste.org/webob/news>`_,
packages=['webob'],
zip_safe=True,
test_suite='nose.collector',
- tests_require=['Tempita', 'WSGIProxy', 'WebTest', 'dtopt', 'nose',
- 'coverage', 'repoze.profile'],
+ tests_require=['nose', 'WebTest'],
)
diff --git a/test b/test
deleted file mode 100755
index 5e12388..0000000
--- a/test
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-NOSE_WITH_DOCTEST=t
-export NOSE_WITH_DOCTEST
-NOSE_DOCTEST_EXTENSION=txt
-export NOSE_DOCTEST_EXTENSION
-#NOSE_DETAILED_ERRORS=t
-#export NOSE_DETAILED_ERRORS
-nosetests $*
diff --git a/tests/performance_test.py b/tests/performance_test.py
index 02fba96..79f01af 100644
--- a/tests/performance_test.py
+++ b/tests/performance_test.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python
import webob
-from repoze.profile.profiler import AccumulatingProfileMiddleware
def make_middleware(app):
+ from repoze.profile.profiler import AccumulatingProfileMiddleware
return AccumulatingProfileMiddleware(
app,
log_filename='/tmp/profile.log',
@@ -11,7 +11,6 @@ def make_middleware(app):
path='/__profile__')
def simple_app(environ, start_response):
- req = webob.Request(environ)
resp = webob.Response('Hello world!')
return resp(environ, start_response)
diff --git a/tests/test_acceptparse.py b/tests/test_acceptparse.py
new file mode 100644
index 0000000..07ae07a
--- /dev/null
+++ b/tests/test_acceptparse.py
@@ -0,0 +1,387 @@
+from unittest import TestCase
+
+class Test_parse_accept_badq(TestCase):
+ from webob.acceptparse import parse_accept
+ assert parse_accept("value1; q=0.1.2") == [('value1', 1)]
+
+
+class TestAccept(TestCase):
+ def Accept(self, *args, **kwargs):
+ from webob.acceptparse import Accept
+ return Accept(*args, **kwargs)
+
+ def test_init_accept_content_type(self):
+ name, value = ('Content-Type', 'text/html')
+ accept = self.Accept(name, value)
+ assert accept.header_name == name
+ assert accept.header_value == value
+ assert accept._parsed == [('text/html', 1)]
+
+ def test_init_accept_accept_charset(self):
+ name, value = ('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')
+ accept = self.Accept(name, value)
+ assert accept.header_name == name
+ assert accept.header_value == value
+ assert accept._parsed == [('iso-8859-5', 1),
+ ('unicode-1-1', 0.80000000000000004),
+ ('iso-8859-1', 1)]
+
+ def test_init_accept_accept_charset_with_iso_8859_1(self):
+ name, value = ('Accept-Charset', 'iso-8859-1')
+ accept = self.Accept(name, value)
+ assert accept.header_name == name
+ assert accept.header_value == value
+ assert accept._parsed == [('iso-8859-1', 1)]
+
+ def test_init_accept_accept_charset_wildcard(self):
+ name, value = ('Accept-Charset', '*')
+ accept = self.Accept(name, value)
+ assert accept.header_name == name
+ assert accept.header_value == value
+ assert accept._parsed == [('*', 1)]
+
+ def test_init_accept_accept_language(self):
+ name, value = ('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')
+ accept = self.Accept(name, value)
+ assert accept.header_name == name
+ assert accept.header_value == value
+ assert accept._parsed == [('da', 1),
+ ('en-gb', 0.80000000000000004),
+ ('en', 0.69999999999999996)]
+
+ def test_init_accept_invalid_value(self):
+ name, value = ('Accept-Language', 'da, q, en-gb;q=0.8')
+ accept = self.Accept(name, value)
+ # The "q" value should not be there.
+ assert accept._parsed == [('da', 1),
+ ('en-gb', 0.80000000000000004)]
+
+ def test_init_accept_invalid_q_value(self):
+ name, value = ('Accept-Language', 'da, en-gb;q=foo')
+ accept = self.Accept(name, value)
+ # I can't get to cover line 40-41 (webob.acceptparse) as the regex
+ # will prevent from hitting these lines (aconrad)
+ assert accept._parsed == [('da', 1), ('en-gb', 1)]
+
+ def test_accept_repr(self):
+ name, value = ('Content-Type', 'text/html')
+ accept = self.Accept(name, value)
+ assert repr(accept) == '<%s at 0x%x %s: %s>' % ('Accept',
+ abs(id(accept)),
+ name,
+ str(accept))
+
+ def test_accept_str(self):
+ name, value = ('Content-Type', 'text/html')
+ accept = self.Accept(name, value)
+ assert str(accept) == value
+
+ def test_accept_str_with_q_not_1(self):
+ name, value = ('Content-Type', 'text/html;q=0.5')
+ accept = self.Accept(name, value)
+ assert str(accept) == value
+
+ def test_accept_str_with_q_not_1_multiple(self):
+ name, value = ('Content-Type', 'text/html;q=0.5, foo/bar')
+ accept = self.Accept(name, value)
+ assert str(accept) == value
+
+ def test_accept_add_other_accept(self):
+ accept = self.Accept('Content-Type', 'text/html') + \
+ self.Accept('Content-Type', 'foo/bar')
+ assert str(accept) == 'text/html, foo/bar'
+ accept += self.Accept('Content-Type', 'bar/baz;q=0.5')
+ assert str(accept) == 'text/html, foo/bar, bar/baz;q=0.5'
+
+ def test_accept_add_other_list_of_tuples(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ accept += [('foo/bar', 1)]
+ assert str(accept) == 'text/html, foo/bar'
+ accept += [('bar/baz', 0.5)]
+ assert str(accept) == 'text/html, foo/bar, bar/baz;q=0.5'
+ accept += ['she/bangs', 'the/house']
+ assert str(accept) == ('text/html, foo/bar, bar/baz;q=0.5, '
+ 'she/bangs, the/house')
+
+ def test_accept_add_other_dict(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ accept += {'foo/bar': 1}
+ assert str(accept) == 'text/html, foo/bar'
+ accept += {'bar/baz': 0.5}
+ assert str(accept) == 'text/html, foo/bar, bar/baz;q=0.5'
+
+ def test_accept_add_other_empty_str(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ accept += ''
+ assert str(accept) == 'text/html'
+
+ def test_accept_with_no_value_add_other_str(self):
+ accept = self.Accept('Content-Type', '')
+ accept += 'text/html'
+ assert str(accept) == 'text/html'
+
+ def test_contains(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ assert 'text/html' in accept
+
+ def test_contains_not(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ assert not 'foo/bar' in accept
+
+ def test_quality(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ assert accept.quality('text/html') == 1
+ accept = self.Accept('Content-Type', 'text/html;q=0.5')
+ assert accept.quality('text/html') == 0.5
+
+ def test_quality_not_found(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ assert accept.quality('foo/bar') is None
+
+ def test_first_match(self):
+ accept = self.Accept('Content-Type', 'text/html, foo/bar')
+ assert accept.first_match(['text/html', 'foo/bar']) == 'text/html'
+ assert accept.first_match(['foo/bar', 'text/html']) == 'foo/bar'
+ assert accept.first_match(['xxx/xxx', 'text/html']) == 'text/html'
+ assert accept.first_match(['xxx/xxx']) == 'xxx/xxx'
+ assert accept.first_match([None, 'text/html']) is None
+ assert accept.first_match(['text/html', None]) == 'text/html'
+ assert accept.first_match(['foo/bar', None]) == 'foo/bar'
+ self.assertRaises(ValueError, accept.first_match, [])
+
+ def test_best_match(self):
+ accept = self.Accept('Content-Type', 'text/html, foo/bar')
+ assert accept.best_match(['text/html', 'foo/bar']) == 'text/html'
+ assert accept.best_match(['foo/bar', 'text/html']) == 'foo/bar'
+ assert accept.best_match([('foo/bar', 0.5),
+ 'text/html']) == 'text/html'
+ assert accept.best_match([('foo/bar', 0.5),
+ ('text/html', 0.4)]) == 'foo/bar'
+ self.assertRaises(ValueError, accept.best_match, ['text/*'])
+
+ def test_best_match_with_one_lower_q(self):
+ accept = self.Accept('Content-Type', 'text/html, foo/bar;q=0.5')
+ assert accept.best_match(['text/html', 'foo/bar']) == 'text/html'
+ accept = self.Accept('Content-Type', 'text/html;q=0.5, foo/bar')
+ assert accept.best_match(['text/html', 'foo/bar']) == 'foo/bar'
+
+ def test_best_matches(self):
+ accept = self.Accept('Content-Type', 'text/html, foo/bar')
+ assert accept.best_matches() == ['text/html', 'foo/bar']
+ accept = self.Accept('Content-Type', 'text/html, foo/bar;q=0.5')
+ assert accept.best_matches() == ['text/html', 'foo/bar']
+ accept = self.Accept('Content-Type', 'text/html;q=0.5, foo/bar')
+ assert accept.best_matches() == ['foo/bar', 'text/html']
+
+ def test_best_matches_with_fallback(self):
+ accept = self.Accept('Content-Type', 'text/html, foo/bar')
+ assert accept.best_matches('xxx/yyy') == ['text/html',
+ 'foo/bar',
+ 'xxx/yyy']
+ accept = self.Accept('Content-Type', 'text/html;q=0.5, foo/bar')
+ assert accept.best_matches('xxx/yyy') == ['foo/bar',
+ 'text/html',
+ 'xxx/yyy']
+ assert accept.best_matches('foo/bar') == ['foo/bar']
+ assert accept.best_matches('text/html') == ['foo/bar', 'text/html']
+
+ def test_accept_match(self):
+ accept = self.Accept('Content-Type', 'text/html')
+ #FIXME: Accept._match should be standalone function _match that is
+ # attached as Accept._match during Accept.__init__.
+ assert accept._match('*', 'text/html')
+ assert accept._match('text/html', 'text/html')
+ assert accept._match('TEXT/HTML', 'text/html')
+ assert not accept._match('foo/bar', 'text/html')
+
+ def test_accept_match_lang(self):
+ accept = self.Accept('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')
+ #FIXME: Accept._match_lang should be standalone function _match_lang
+ # that is attached as Accept._match during Accept.__init__.
+ assert accept._match('*', 'da')
+ assert accept._match('da', 'DA')
+ assert accept._match('en', 'en-gb')
+ assert accept._match('en-gb', 'en-gb')
+ assert not accept._match('en-gb', 'fr-fr')
+
+
+class TestNilAccept(TestCase):
+ def NilAccept(self, *args, **kwargs):
+ from webob.acceptparse import NilAccept
+ return NilAccept(*args, **kwargs)
+
+ def Accept(self, *args, **kwargs):
+ from webob.acceptparse import Accept
+ return Accept(*args, **kwargs)
+
+ def test_init(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ assert nilaccept.header_name == 'Connection-Close'
+
+ def test_repr(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ assert repr(nilaccept) == ("<NilAccept for Connection-Close: <class "
+ "'webob.acceptparse.Accept'>>")
+
+ def test_str(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ assert str(nilaccept) == ''
+
+ def test_nonzero(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ assert not nilaccept
+
+ def test_add(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ accept = self.Accept('Content-Type', 'text/html')
+ assert nilaccept + accept is accept
+ new_accept = nilaccept + nilaccept
+ assert isinstance(new_accept, accept.__class__)
+ assert new_accept.header_name == 'Connection-Close'
+ assert new_accept.header_value == ''
+ new_accept = nilaccept + 'foo'
+ assert isinstance(new_accept, accept.__class__)
+ assert new_accept.header_name == 'Connection-Close'
+ assert new_accept.header_value == 'foo'
+
+ def test_radd(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ accept = self.Accept('Content-Type', 'text/html')
+ assert isinstance('foo' + nilaccept, accept.__class__)
+ assert ('foo' + nilaccept).header_value == 'foo'
+ # How to test ``if isinstance(item, self.MasterClass): return item``
+ # under NilAccept.__radd__ ??
+
+ def test_radd_masterclass(self):
+ # Is this "reaching into" __radd__ legit?
+ nilaccept = self.NilAccept('Connection-Close')
+ accept = self.Accept('Content-Type', 'text/html')
+ assert nilaccept.__radd__(accept) is accept
+
+ def test_contains(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ # NilAccept.__contains__ always returns True
+ assert '' in nilaccept
+ assert 'dummy' in nilaccept
+ assert nilaccept in nilaccept
+
+ def test_quality(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ # NilAccept.quality always returns 0
+ assert nilaccept.quality('dummy') == 0
+
+ def test_first_match(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ # NilAccept.first_match always returns element 0 of the list
+ assert nilaccept.first_match(['dummy', '']) == 'dummy'
+ assert nilaccept.first_match(['', 'dummy']) == ''
+
+ def test_best_match(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ assert nilaccept.best_match(['foo', 'bar']) == 'foo'
+ assert nilaccept.best_match([('foo', 1), ('bar', 0.5)]) == 'foo'
+ assert nilaccept.best_match([('foo', 0.5), ('bar', 1)]) == 'bar'
+ assert nilaccept.best_match([('foo', 0.5), 'bar']) == 'bar'
+ # default_match has no effect on NilAccept class
+ assert nilaccept.best_match([('foo', 0.5), 'bar'],
+ default_match=True) == 'bar'
+ assert nilaccept.best_match([('foo', 0.5), 'bar'],
+ default_match=False) == 'bar'
+
+ def test_best_matches(self):
+ nilaccept = self.NilAccept('Connection-Close')
+ assert nilaccept.best_matches() == []
+ assert nilaccept.best_matches('foo') == ['foo']
+
+
+class TestNoAccept(TestCase):
+ def NoAccept(self, *args, **kwargs):
+ from webob.acceptparse import NoAccept
+ return NoAccept(*args, **kwargs)
+
+ def test_contains(self):
+ noaccept = self.NoAccept('Connection-Close')
+ # NoAccept.__contains__ always returns False
+ assert not '' in noaccept
+ assert not True in noaccept
+ assert not False in noaccept
+ assert not noaccept in noaccept
+
+
+class TestMIMEAccept(TestCase):
+ def MIMEAccept(self, *args, **kwargs):
+ from webob.acceptparse import MIMEAccept
+ return MIMEAccept(*args, **kwargs)
+
+ def test_init(self):
+ mimeaccept = self.MIMEAccept('Content-Type', 'image/jpg')
+ assert mimeaccept._parsed == [('image/jpg', 1)]
+ mimeaccept = self.MIMEAccept('Content-Type', 'image/png, image/jpg;q=0.5')
+ assert mimeaccept._parsed == [('image/png', 1), ('image/jpg', 0.5)]
+ mimeaccept = self.MIMEAccept('Content-Type', 'image, image/jpg;q=0.5')
+ assert mimeaccept._parsed == [('image/jpg', 0.5)]
+ mimeaccept = self.MIMEAccept('Content-Type', '*/*')
+ assert mimeaccept._parsed == [('*/*', 1)]
+ mimeaccept = self.MIMEAccept('Content-Type', '*/png')
+ assert mimeaccept._parsed == []
+ mimeaccept = self.MIMEAccept('Content-Type', 'image/*')
+ assert mimeaccept._parsed == [('image/*', 1)]
+
+ def test_accept_html(self):
+ mimeaccept = self.MIMEAccept('Content-Type', 'image/jpg')
+ assert not mimeaccept.accept_html()
+ mimeaccept = self.MIMEAccept('Content-Type', 'image/jpg, text/html')
+ assert mimeaccept.accept_html()
+
+ def test_match(self):
+ mimeaccept = self.MIMEAccept('Content-Type', 'image/jpg')
+ assert mimeaccept._match('image/jpg', 'image/jpg')
+ assert mimeaccept._match('image/*', 'image/jpg')
+ assert mimeaccept._match('*/*', 'image/jpg')
+ assert not mimeaccept._match('text/html', 'image/jpg')
+ self.assertRaises(AssertionError, mimeaccept._match, 'image/jpg', '*/*')
+
+
+class TestAcceptProperty(TestCase):
+ def test_accept_property_fget(self):
+ from webob.acceptparse import accept_property
+ from webob import Request
+ desc = accept_property('Accept-Charset', '14.2')
+ req = Request.blank('/', environ={'envkey': 'envval'})
+ desc.fset(req, 'val')
+ self.assertEqual(desc.fget(req).header_value, 'val')
+
+ def test_accept_property_fget_nil(self):
+ from webob.acceptparse import NilAccept
+ from webob.acceptparse import accept_property
+ from webob import Request
+ desc = accept_property('Accept-Charset', '14.2')
+ req = Request.blank('/')
+ self.assertEqual(type(desc.fget(req)), NilAccept)
+
+ def test_accept_property_fset(self):
+ from webob.acceptparse import accept_property
+ from webob import Request
+ desc = accept_property('Accept-Charset', '14.2')
+ req = Request.blank('/', environ={'envkey': 'envval'})
+ desc.fset(req, 'baz')
+ self.assertEqual(desc.fget(req).header_value, 'baz')
+
+ def test_accept_property_fset_acceptclass(self):
+ from webob.acceptparse import accept_property
+ from webob import Request
+ desc = accept_property('Accept-Charset', '14.2')
+ req = Request.blank('/', environ={'envkey': 'envval'})
+ desc.fset(req, ['utf-8', 'latin-11'])
+ self.assertEqual(desc.fget(req).header_value, 'utf-8, latin-11, iso-8859-1')
+
+ def test_accept_property_fdel(self):
+ from webob.acceptparse import NilAccept
+ from webob.acceptparse import accept_property
+ from webob import Request
+ desc = accept_property('Accept-Charset', '14.2')
+ req = Request.blank('/', environ={'envkey': 'envval'})
+ desc.fset(req, 'val')
+ assert desc.fget(req).header_value == 'val'
+ desc.fdel(req)
+ self.assertEqual(type(desc.fget(req)), NilAccept)
diff --git a/tests/test_byterange.py b/tests/test_byterange.py
new file mode 100644
index 0000000..daad184
--- /dev/null
+++ b/tests/test_byterange.py
@@ -0,0 +1,231 @@
+from webob.byterange import Range, ContentRange, _is_content_range_valid
+
+from nose.tools import assert_true, assert_false, assert_equal, assert_raises
+
+# Range class
+
+def test_satisfiable():
+ range = Range( ((0,99),) )
+ assert_true( range.satisfiable(100) )
+ assert_false( range.satisfiable(99) )
+
+def test_range_for_length():
+ range = Range( ((0,99), (100,199) ) )
+ assert_equal( range.range_for_length( 'None'), None )
+
+def test_range_content_range_length_none():
+ range = Range( ((0, 100),) )
+ assert_equal( range.content_range( None ), None )
+
+def test_range_content_range_length_ok():
+ range = Range( ((0, 100),) )
+ assert_true( range.content_range( 100 ).__class__, ContentRange )
+
+def test_range_for_length_more_than_one_range():
+ # More than one range
+ range = Range( ((0,99), (100,199) ) )
+ assert_equal( range.range_for_length(100), None )
+
+def test_range_for_length_one_range_and_length_none():
+ # One range and length is None
+ range = Range( ((0,99), ) )
+ assert_equal( range.range_for_length( None ), None )
+
+def test_range_for_length_end_is_none():
+ # End is None
+ range = Range( ((0, None), ) )
+ assert_equal( range.range_for_length(100), (0,100) )
+
+def test_range_for_length_end_is_none_negative_start():
+ # End is None and start is negative
+ range = Range( ((-5, None), ) )
+ assert_equal( range.range_for_length(100), (95,100) )
+
+def test_range_start_none():
+ # Start is None
+ range = Range( ((None, 99), ) )
+ assert_equal( range.range_for_length(100), None )
+
+def test_range_str_end_none():
+ range = Range( ((0, 100), ) )
+ # Manually set test values
+ range.ranges = ( (0, None), )
+ assert_equal( range.__str__(), 'bytes=0-' )
+
+def test_range_str_end_none_negative_start():
+ range = Range( ((0, 100), ) )
+ # Manually set test values
+ range.ranges = ( (-5, None), )
+ assert_equal( range.__str__(), 'bytes=-5' )
+
+def test_range_str_1():
+ # Single range
+ range = Range( ((0, 100), ) )
+ assert_equal( str(range), 'bytes=0-99' )
+
+def test_range_str_2():
+ # Two ranges
+ range = Range( ((0, 100), (101, 200) ) )
+ assert_equal( str(range), 'bytes=0-99,101-199' )
+
+def test_range_str_3():
+ # Negative start
+ range = Range( ((-1, 100),) )
+ assert_raises( ValueError, range.__str__ )
+
+def test_range_str_4():
+ # Negative end
+ range = Range( ((0, 100),) )
+ # Manually set test values
+ range.ranges = ( (0, -100), )
+ assert_raises( ValueError, range.__str__ )
+
+def test_range_repr():
+ range = Range( ((0, 99),) )
+ assert_true( range.__repr__(), '<Range bytes 0-98>' )
+
+def test_parse_valid_input():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse( 'bytes=0-99' ).__class__, Range )
+
+def test_parse_missing_equals_sign():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse( 'bytes 0-99' ), None )
+
+def test_parse_invalid_units():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse( 'words=0-99' ), None )
+
+def test_parse_bytes_valid_input():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=0-99' ), ('bytes', [(0,100)] ) )
+
+def test_parse_bytes_missing_equals_sign():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes 0-99'), None )
+
+def test_parse_bytes_missing_dash():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=0 99'), None )
+
+def test_parse_bytes_null_input():
+ range = Range( ((0, 100),) )
+ assert_raises( TypeError, range.parse_bytes, '' )
+
+def test_parse_bytes_two_many_ranges():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=-100,-100' ), None )
+
+def test_parse_bytes_negative_start():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=-0-99' ), None )
+
+def test_parse_bytes_start_greater_than_end():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=99-0'), None )
+
+def test_parse_bytes_start_greater_than_last_end():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=0-99,0-199'), None )
+
+def test_parse_bytes_only_start():
+ range = Range( ((0, 100),) )
+ assert_equal( range.parse_bytes( 'bytes=0-'), ('bytes', [(0, None)]) )
+
+# ContentRange class
+
+def test_contentrange_bad_input():
+ assert_raises( ValueError, ContentRange, None, 99, None )
+
+def test_contentrange_repr():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_true( contentrange.__repr__(), '<ContentRange bytes 0-98/100>' )
+
+def test_contentrange_str_length_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ contentrange.length = None
+ assert_equal( contentrange.__str__(), 'bytes 0-98/*' )
+
+def test_contentrange_str_start_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ contentrange.start = None
+ contentrange.stop = None
+ assert_equal( contentrange.__str__(), 'bytes */100' )
+
+def test_contentrange_iter():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_true( type(contentrange.__iter__()), iter )
+
+def test_cr_parse_ok():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_true( contentrange.parse( 'bytes 0-99/100' ).__class__, ContentRange )
+
+def test_cr_parse_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( None ), None )
+
+def test_cr_parse_no_bytes():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( '0-99 100' ), None )
+
+def test_cr_parse_missing_slash():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes 0-99 100' ), None )
+
+def test_cr_parse_invalid_length():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes 0-99/xxx' ), None )
+
+def test_cr_parse_no_range():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes 0 99/100' ), None )
+
+def test_cr_parse_range_star():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes */100' ).__class__, ContentRange )
+
+def test_cr_parse_parse_problem_1():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes A-99/100' ), None )
+
+def test_cr_parse_parse_problem_2():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes 0-B/100' ), None )
+
+def test_cr_parse_content_invalid():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse( 'bytes 99-0/100' ), None )
+
+def test_contentrange_str_length_start():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( contentrange.parse('bytes 0 99/*'), None )
+
+# _is_content_range_valid function
+
+def test_is_content_range_valid_start_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( None, 99, 90), False )
+
+def test_is_content_range_valid_stop_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( 99, None, 90), False )
+
+def test_is_content_range_valid_start_stop_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( None, None, 90), True )
+
+def test_is_content_range_valid_start_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( None, 99, 90), False )
+
+def test_is_content_range_valid_length_none():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( 0, 99, None), True )
+
+def test_is_content_range_valid_stop_greater_than_length_response():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( 0, 99, 90, response=True), False )
+
+def test_is_content_range_valid_stop_greater_than_length():
+ contentrange = ContentRange( 0, 99, 100 )
+ assert_equal( _is_content_range_valid( 0, 99, 90), True )
diff --git a/tests/test_cachecontrol.py b/tests/test_cachecontrol.py
new file mode 100644
index 0000000..2388020
--- /dev/null
+++ b/tests/test_cachecontrol.py
@@ -0,0 +1,266 @@
+from nose.tools import eq_
+from nose.tools import raises
+import unittest
+
+
+def test_cache_control_object_max_age_None():
+ from webob.cachecontrol import CacheControl
+ cc = CacheControl({}, 'a')
+ cc.properties['max-age'] = None
+ eq_(cc.max_age, -1)
+
+
+class TestUpdateDict(unittest.TestCase):
+
+ def setUp(self):
+ self.call_queue = []
+ def callback(args):
+ self.call_queue.append("Called with: %s" % repr(args))
+ self.callback = callback
+
+ def make_one(self, callback):
+ from webob.cachecontrol import UpdateDict
+ ud = UpdateDict()
+ ud.updated = callback
+ return ud
+
+ def test_clear(self):
+ newone = self.make_one(self.callback)
+ newone['first'] = 1
+ assert len(newone) == 1
+ newone.clear()
+ assert len(newone) == 0
+
+ def test_update(self):
+ newone = self.make_one(self.callback)
+ d = {'one' : 1 }
+ newone.update(d)
+ assert newone == d
+
+ def test_set_delete(self):
+ newone = self.make_one(self.callback)
+ newone['first'] = 1
+ assert len(self.call_queue) == 1
+ assert self.call_queue[-1] == "Called with: {'first': 1}"
+
+ del newone['first']
+ assert len(self.call_queue) == 2
+ assert self.call_queue[-1] == 'Called with: {}'
+
+ def test_setdefault(self):
+ newone = self.make_one(self.callback)
+ assert newone.setdefault('haters', 'gonna-hate') == 'gonna-hate'
+ assert len(self.call_queue) == 1
+ assert self.call_queue[-1] == "Called with: {'haters': 'gonna-hate'}", self.call_queue[-1]
+
+ # no effect if failobj is not set
+ assert newone.setdefault('haters', 'gonna-love') == 'gonna-hate'
+ assert len(self.call_queue) == 1
+
+ def test_pop(self):
+ newone = self.make_one(self.callback)
+ newone['first'] = 1
+ newone.pop('first')
+ assert len(self.call_queue) == 2
+ assert self.call_queue[-1] == 'Called with: {}', self.call_queue[-1]
+
+ def test_popitem(self):
+ newone = self.make_one(self.callback)
+ newone['first'] = 1
+ assert newone.popitem() == ('first', 1)
+ assert len(self.call_queue) == 2
+ assert self.call_queue[-1] == 'Called with: {}', self.call_queue[-1]
+
+ def test_callback_args(self):
+ assert True
+ #assert False
+
+
+class TestExistProp(unittest.TestCase):
+ """
+ Test webob.cachecontrol.exists_property
+ """
+
+ def setUp(self):
+ pass
+
+ def make_one(self):
+ from webob.cachecontrol import exists_property
+
+ class Dummy(object):
+ properties = dict(prop=1)
+ type = 'dummy'
+ prop = exists_property('prop', 'dummy')
+ badprop = exists_property('badprop', 'big_dummy')
+
+ return Dummy
+
+ def test_get_on_class(self):
+ from webob.cachecontrol import exists_property
+ Dummy = self.make_one()
+ assert isinstance(Dummy.prop, exists_property), Dummy.prop
+
+ def test_get_on_instance(self):
+ obj = self.make_one()()
+ assert obj.prop is True
+
+ @raises(AttributeError)
+ def test_type_mismatch_raise(self):
+ obj = self.make_one()()
+ obj.badprop = True
+
+ def test_set_w_value(self):
+ obj = self.make_one()()
+ obj.prop = True
+ assert obj.prop is True
+ assert obj.properties['prop'] is None
+
+ def test_del_value(self):
+ obj = self.make_one()()
+ del obj.prop
+ assert not 'prop' in obj.properties
+
+
+class TestValueProp(unittest.TestCase):
+ """
+ Test webob.cachecontrol.exists_property
+ """
+
+ def setUp(self):
+ pass
+
+ def make_one(self):
+ from webob.cachecontrol import value_property
+
+ class Dummy(object):
+ properties = dict(prop=1)
+ type = 'dummy'
+ prop = value_property('prop', 'dummy')
+ badprop = value_property('badprop', 'big_dummy')
+
+ return Dummy
+
+ def test_get_on_class(self):
+ from webob.cachecontrol import value_property
+ Dummy = self.make_one()
+ assert isinstance(Dummy.prop, value_property), Dummy.prop
+
+ def test_get_on_instance(self):
+ dummy = self.make_one()()
+ assert dummy.prop, dummy.prop
+ #assert isinstance(Dummy.prop, value_property), Dummy.prop
+
+ def test_set_on_instance(self):
+ dummy = self.make_one()()
+ dummy.prop = "new"
+ assert dummy.prop == "new", dummy.prop
+ assert dummy.properties['prop'] == "new", dict(dummy.properties)
+
+ def test_set_on_instance_bad_attribute(self):
+ dummy = self.make_one()()
+ dummy.prop = "new"
+ assert dummy.prop == "new", dummy.prop
+ assert dummy.properties['prop'] == "new", dict(dummy.properties)
+
+ def test_set_wrong_type(self):
+ from webob.cachecontrol import value_property
+ class Dummy(object):
+ properties = dict(prop=1, type='fail')
+ type = 'dummy'
+ prop = value_property('prop', 'dummy', type='failingtype')
+ dummy = Dummy()
+ def assign():
+ dummy.prop = 'foo'
+ self.assertRaises(AttributeError, assign)
+
+ def test_set_type_true(self):
+ dummy = self.make_one()()
+ dummy.prop = True
+ self.assertEquals(dummy.prop, None)
+
+ def test_set_on_instance_w_default(self):
+ dummy = self.make_one()()
+ dummy.prop = "dummy"
+ assert dummy.prop == "dummy", dummy.prop
+ #@@ this probably needs more tests
+
+ def test_del(self):
+ dummy = self.make_one()()
+ dummy.prop = 'Ian Bicking likes to skip'
+ del dummy.prop
+ assert dummy.prop == "dummy", dummy.prop
+
+
+def test_copy_cc():
+ from webob.cachecontrol import CacheControl
+ cc = CacheControl({'header':'%', "msg":'arewerichyet?'}, 'request')
+ cc2 = cc.copy()
+ assert cc.properties is not cc2.properties
+ assert cc.type is cc2.type
+
+# 212
+
+def test_serialize_cache_control_emptydict():
+ from webob.cachecontrol import serialize_cache_control
+ result = serialize_cache_control(dict())
+ assert result == ''
+
+def test_serialize_cache_control_cache_control_object():
+ from webob.cachecontrol import serialize_cache_control, CacheControl
+ result = serialize_cache_control(CacheControl({}, 'request'))
+ assert result == ''
+
+def test_serialize_cache_control_object_with_headers():
+ from webob.cachecontrol import serialize_cache_control, CacheControl
+ result = serialize_cache_control(CacheControl({'header':'a'}, 'request'))
+ assert result == 'header=a'
+
+def test_serialize_cache_control_value_is_None():
+ from webob.cachecontrol import serialize_cache_control, CacheControl
+ result = serialize_cache_control(CacheControl({'header':None}, 'request'))
+ assert result == 'header'
+
+def test_serialize_cache_control_value_needs_quote():
+ from webob.cachecontrol import serialize_cache_control, CacheControl
+ result = serialize_cache_control(CacheControl({'header':'""'}, 'request'))
+ assert result == 'header=""""'
+
+class TestCacheControl(unittest.TestCase):
+ def make_one(self, props, typ):
+ from webob.cachecontrol import CacheControl
+ return CacheControl(props, typ)
+
+ def test_ctor(self):
+ cc = self.make_one({'a':1}, 'typ')
+ self.assertEquals(cc.properties, {'a':1})
+ self.assertEquals(cc.type, 'typ')
+
+ def test_parse(self):
+ from webob.cachecontrol import CacheControl
+ cc = CacheControl.parse("public, max-age=315360000")
+ self.assertEquals(type(cc), CacheControl)
+ self.assertEquals(cc.max_age, 315360000)
+ self.assertEquals(cc.public, True)
+
+ def test_parse_updates_to(self):
+ from webob.cachecontrol import CacheControl
+ def foo(arg): return { 'a' : 1 }
+ cc = CacheControl.parse("public, max-age=315360000", updates_to=foo)
+ self.assertEquals(type(cc), CacheControl)
+ self.assertEquals(cc.max_age, 315360000)
+
+ def test_parse_valueerror_int(self):
+ from webob.cachecontrol import CacheControl
+ def foo(arg): return { 'a' : 1 }
+ cc = CacheControl.parse("public, max-age=abc")
+ self.assertEquals(type(cc), CacheControl)
+ self.assertEquals(cc.max_age, 'abc')
+
+ def test_repr(self):
+ cc = self.make_one({'a':'1'}, 'typ')
+ result = repr(cc)
+ self.assertEqual(result, "<CacheControl 'a=1'>")
+
+
+
+
diff --git a/tests/test_cookies.py b/tests/test_cookies.py
index 9a66101..b985fce 100644
--- a/tests/test_cookies.py
+++ b/tests/test_cookies.py
@@ -1,20 +1,21 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from datetime import timedelta
from webob import cookies
-from nose.tools import ok_, assert_raises, eq_
+from nose.tools import eq_
-def test_cookie():
- """
- Test cookie parsing, serialization and `repr`
- """
+def test_cookie_empty():
c = cookies.Cookie() # empty cookie
eq_(repr(c), '<Cookie: []>')
- # a cookie with one value
+
+def test_cookie_one_value():
c = cookies.Cookie('dismiss-top=6')
eq_(repr(c), "<Cookie: [<Morsel: dismiss-top='6'>]>")
+
+def test_cookie_one_value_with_trailing_semi():
c = cookies.Cookie('dismiss-top=6;')
eq_(repr(c), "<Cookie: [<Morsel: dismiss-top='6'>]>")
- # more complex cookie, (also mixing commas and semicolons)
+
+def test_cookie_complex():
c = cookies.Cookie('dismiss-top=6; CP=null*, '\
'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,"')
c_dict = dict((k,v.value) for k,v in c.items())
@@ -23,10 +24,32 @@ def test_cookie():
'PHPSESSID': '0a539d42abc001cdc762809248d4beed',
'dismiss-top': '6'
})
+
+def test_cookie_complex_serialize():
+ c = cookies.Cookie('dismiss-top=6; CP=null*, '\
+ 'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,"')
eq_(c.serialize(),
'CP=null*, PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,", '
'dismiss-top=6')
- # reserved keys ($xx)
+
+def test_cookie_load_multiple():
+ c = cookies.Cookie('a=1; Secure=true')
+ eq_(repr(c), "<Cookie: [<Morsel: a='1'>]>")
+ eq_(c['a']['secure'], 'true')
+
+def test_cookie_secure():
+ c = cookies.Cookie()
+ c['foo'] = 'bar'
+ c['foo'].secure = True
+ eq_(c.serialize(), 'foo=bar; secure')
+
+def test_cookie_httponly():
+ c = cookies.Cookie()
+ c['foo'] = 'bar'
+ c['foo'].httponly = True
+ eq_(c.serialize(), 'foo=bar; HttpOnly')
+
+def test_cookie_reserved_keys():
c = cookies.Cookie('dismiss-top=6; CP=null*; $version=42; a=42')
assert '$version' not in c
c = cookies.Cookie('$reserved=42; a=$42')
@@ -37,20 +60,15 @@ def test_serialize_cookie_date():
Testing webob.cookies.serialize_cookie_date.
Missing scenarios:
* input value is an str, should be returned verbatim
- * input value is an int, should be converted to timedelta and we should
- continue the rest of the process
+ * input value is an int, should be converted to timedelta and we
+ should continue the rest of the process
"""
- ok_(cookies.serialize_cookie_date('Tue, 04-Jan-2011 13:43:50 GMT')==\
- 'Tue, 04-Jan-2011 13:43:50 GMT', 'We passed a string, should get the '
- 'same one')
- ok_(cookies.serialize_cookie_date(None) is None, 'We passed None, should '
- 'get None')
-
+ eq_(cookies.serialize_cookie_date('Tue, 04-Jan-2011 13:43:50 GMT'),
+ 'Tue, 04-Jan-2011 13:43:50 GMT')
+ eq_(cookies.serialize_cookie_date(None), None)
cdate_delta = cookies.serialize_cookie_date(timedelta(seconds=10))
cdate_int = cookies.serialize_cookie_date(10)
- eq_(cdate_delta, cdate_int,
- 'Passing a int to method should return the same result as passing a timedelta'
- )
+ eq_(cdate_delta, cdate_int)
def test_ch_unquote():
eq_(cookies._unquote(u'"hello world'), u'"hello world')
@@ -66,3 +84,30 @@ def test_ch_unquote():
]:
eq_(cookies._unquote(q), unq)
eq_(cookies._quote(unq), q)
+
+def test_cookie_setitem_needs_quoting():
+ c = cookies.Cookie()
+ c['La Pe\xc3\xb1a'] = '1'
+ eq_(len(c), 0)
+
+def test_morsel_serialize_with_expires():
+ morsel = cookies.Morsel('bleh', 'blah')
+ morsel.expires = 'Tue, 04-Jan-2011 13:43:50 GMT'
+ result = morsel.serialize()
+ eq_(result, 'bleh=blah; expires="Tue, 04-Jan-2011 13:43:50 GMT"')
+
+def test_serialize_max_age_timedelta():
+ import datetime
+ val = datetime.timedelta(86400)
+ result = cookies.serialize_max_age(val)
+ eq_(result, '7464960000')
+
+def test_serialize_max_age_int():
+ val = 86400
+ result = cookies.serialize_max_age(val)
+ eq_(result, '86400')
+
+def test_serialize_max_age_str():
+ val = '86400'
+ result = cookies.serialize_max_age(val)
+ eq_(result, '86400')
diff --git a/tests/test_datetime_utils.py b/tests/test_datetime_utils.py
index 3b91808..bcaf541 100644
--- a/tests/test_datetime_utils.py
+++ b/tests/test_datetime_utils.py
@@ -1,11 +1,19 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
+import datetime
import calendar
-from datetime import *
from rfc822 import formatdate
from webob import datetime_utils
from nose.tools import ok_, eq_, assert_raises
+def test_UTC():
+ """Test missing function in _UTC"""
+ x = datetime_utils.UTC
+ ok_(x.tzname(datetime.datetime.now())=='UTC')
+ eq_(x.dst(datetime.datetime.now()), datetime.timedelta(0))
+ eq_(x.utcoffset(datetime.datetime.now()), datetime.timedelta(0))
+ eq_(repr(x), 'UTC')
+
def test_parse_date():
"""Testing datetime_utils.parse_date.
We need to verify the following scenarios:
@@ -30,9 +38,11 @@ def test_parse_date():
"to parse_date. We should get None but instead we got %s" %\
ret)
ret = datetime_utils.parse_date('Mon, 20 Nov 1995 19:12:08 -0500')
- eq_(ret, datetime(1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
+ eq_(ret, datetime.datetime(
+ 1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
ret = datetime_utils.parse_date('Mon, 20 Nov 1995 19:12:08')
- eq_(ret, datetime(1995, 11, 20, 19, 12, 8, tzinfo=datetime_utils.UTC))
+ eq_(ret,
+ datetime.datetime(1995, 11, 20, 19, 12, 8, tzinfo=datetime_utils.UTC))
def test_serialize_date():
"""Testing datetime_utils.serialize_date
@@ -44,8 +54,10 @@ def test_serialize_date():
ret = datetime_utils.serialize_date(u'Mon, 20 Nov 1995 19:12:08 GMT')
assert type(ret) is (str)
eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
- dt = formatdate(calendar.timegm((datetime.now()+timedelta(1)).timetuple()))
- eq_(dt, datetime_utils.serialize_date(timedelta(1)))
+ dt = formatdate(
+ calendar.timegm(
+ (datetime.datetime.now()+datetime.timedelta(1)).timetuple()))
+ eq_(dt, datetime_utils.serialize_date(datetime.timedelta(1)))
assert_raises(ValueError, datetime_utils.serialize_date, None)
def test_parse_date_delta():
@@ -58,7 +70,18 @@ def test_parse_date_delta():
ok_(datetime_utils.parse_date_delta(None) is None, 'Passing none value, '
'should return None')
ret = datetime_utils.parse_date_delta('Mon, 20 Nov 1995 19:12:08 -0500')
- eq_(ret, datetime(1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
+ eq_(ret, datetime.datetime(
+ 1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
+ WHEN = datetime.datetime(2011, 3, 16, 10, 10, 37, tzinfo=datetime_utils.UTC)
+ #with _NowRestorer(WHEN): Dammit, only Python 2.5 w/ __future__
+ nr = _NowRestorer(WHEN)
+ nr.__enter__()
+ try:
+ ret = datetime_utils.parse_date_delta(1)
+ eq_(ret, WHEN + datetime.timedelta(0, 1))
+ finally:
+ nr.__exit__(None, None, None)
+
def test_serialize_date_delta():
"""Testing datetime_utils.serialize_date_delta
@@ -66,13 +89,29 @@ def test_serialize_date_delta():
* if we pass something that's not an int or float, it should delegate
the task to serialize_date
"""
+ eq_(datetime_utils.serialize_date_delta(1), '1')
+ eq_(datetime_utils.serialize_date_delta(1.5), '1')
ret = datetime_utils.serialize_date_delta(u'Mon, 20 Nov 1995 19:12:08 GMT')
assert type(ret) is (str)
eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
-def test_UTC():
- """Test missing function in _UTC"""
- x = datetime_utils.UTC
- ok_(x.tzname(datetime.now())=='UTC')
+def test_timedelta_to_seconds():
+ val = datetime.timedelta(86400)
+ result = datetime_utils.timedelta_to_seconds(val)
+ eq_(result, 7464960000)
+
+
+class _NowRestorer(object):
+
+ def __init__(self, new_NOW):
+ self._new_NOW = new_NOW
+ self._old_NOW = None
+ def __enter__(self):
+ import webob.datetime_utils
+ self._old_NOW = webob.datetime_utils._NOW
+ webob.datetime_utils._NOW = self._new_NOW
+ def __exit__(self, exc_type, exc_value, traceback):
+ import webob.datetime_utils
+ webob.datetime_utils._NOW = self._old_NOW
diff --git a/tests/test_dec.py b/tests/test_dec.py
new file mode 100644
index 0000000..0f65590
--- /dev/null
+++ b/tests/test_dec.py
@@ -0,0 +1,267 @@
+import unittest
+from webob import Request
+from webob import Response
+from webob.dec import _format_args
+from webob.dec import _func_name
+from webob.dec import wsgify
+
+class DecoratorTests(unittest.TestCase):
+ def _testit(self, app, req):
+ if isinstance(req, basestring):
+ req = Request.blank(req)
+ resp = req.get_response(app)
+ return resp
+
+ def test_wsgify(self):
+ resp_str = 'hey, this is a test: %s'
+ @wsgify
+ def test_app(req):
+ return resp_str % req.url
+ resp = self._testit(test_app, '/a url')
+ self.assertEqual(resp.body, resp_str % 'http://localhost/a%20url')
+ self.assertEqual(resp.content_length, 45)
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual('%r' % (test_app,), 'wsgify(tests.test_dec.test_app)')
+
+ def test_wsgify_empty_repr(self):
+ self.assertEqual('%r' % (wsgify(),), 'wsgify()')
+
+ def test_wsgify_args(self):
+ resp_str = 'hey hey my my'
+ @wsgify(args=(resp_str,))
+ def test_app(req, strarg):
+ return strarg
+ resp = self._testit(test_app, '/a url')
+ self.assertEqual(resp.body, resp_str)
+ self.assertEqual(resp.content_length, 13)
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual('%r' % (test_app,),
+ "wsgify(tests.test_dec.test_app, args=('%s',))" % resp_str)
+
+ def test_wsgify_kwargs(self):
+ resp_str = 'hey hey my my'
+ @wsgify(kwargs=dict(strarg=resp_str))
+ def test_app(req, strarg=''):
+ return strarg
+ resp = self._testit(test_app, '/a url')
+ self.assertEqual(resp.body, resp_str)
+ self.assertEqual(resp.content_length, 13)
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual('%r' % test_app,
+ "wsgify(tests.test_dec.test_app, "
+ "kwargs={'strarg': '%s'})" % resp_str)
+
+ def test_wsgify_raise_httpexception(self):
+ from webob.exc import HTTPBadRequest
+ @wsgify
+ def test_app(req):
+ import sys
+ if sys.version_info < (2,5):
+ raise HTTPBadRequest(None).exception
+ raise HTTPBadRequest
+ resp = self._testit(test_app, '/a url')
+ self.assert_(resp.body.startswith('400 Bad Request'))
+ self.assertEqual(resp.content_type, 'text/plain')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual('%r' % test_app,
+ "wsgify(tests.test_dec.test_app)")
+
+ def test_wsgify_no___get__(self):
+ # use a class instance instead of a fn so we wrap something w/
+ # no __get__
+ class TestApp(object):
+ def __call__(self, req):
+ return 'nothing to see here'
+ test_app = wsgify(TestApp())
+ resp = self._testit(test_app, '/a url')
+ self.assertEqual(resp.body, 'nothing to see here')
+ self.assert_(test_app.__get__(test_app) is test_app)
+
+ def test_wsgify_args_no_func(self):
+ test_app = wsgify(None, args=(1,))
+ self.assertRaises(TypeError, self._testit, test_app, '/a url')
+
+ def test_wsgify_wrong_sig(self):
+ @wsgify
+ def test_app(req):
+ return 'What have you done for me lately?'
+ req = dict()
+ self.assertRaises(TypeError, test_app, req, 1, 2)
+ self.assertRaises(TypeError, test_app, req, 1, key='word')
+
+ def test_wsgify_none_response(self):
+ @wsgify
+ def test_app(req):
+ return
+ resp = self._testit(test_app, '/a url')
+ self.assertEqual(resp.body, '')
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.content_length, 0)
+
+ def test_wsgify_get(self):
+ resp_str = "What'choo talkin' about, Willis?"
+ @wsgify
+ def test_app(req):
+ return Response(resp_str)
+ resp = test_app.get('/url/path')
+ self.assertEqual(resp.body, resp_str)
+
+ def test_wsgify_post(self):
+ post_dict = dict(speaker='Robin',
+ words='Holy test coverage, Batman!')
+ @wsgify
+ def test_app(req):
+ return Response('%s: %s' % (req.POST['speaker'],
+ req.POST['words']))
+ resp = test_app.post('/url/path', post_dict)
+ self.assertEqual(resp.body, '%s: %s' % (post_dict['speaker'],
+ post_dict['words']))
+
+ def test_wsgify_request_method(self):
+ resp_str = 'Nice body!'
+ @wsgify
+ def test_app(req):
+ self.assertEqual(req.method, 'PUT')
+ return Response(req.body)
+ resp = test_app.request('/url/path', method='PUT',
+ body=resp_str)
+ self.assertEqual(resp.body, resp_str)
+ self.assertEqual(resp.content_length, 10)
+ self.assertEqual(resp.content_type, 'text/html')
+
+ def test_wsgify_undecorated(self):
+ def test_app(req):
+ return Response('whoa')
+ wrapped_test_app = wsgify(test_app)
+ self.assert_(wrapped_test_app.undecorated is test_app)
+
+ def test_wsgify_custom_request(self):
+ resp_str = 'hey, this is a test: %s'
+ class MyRequest(Request):
+ pass
+ @wsgify(RequestClass=MyRequest)
+ def test_app(req):
+ return resp_str % req.url
+ resp = self._testit(test_app, '/a url')
+ self.assertEqual(resp.body, resp_str % 'http://localhost/a%20url')
+ self.assertEqual(resp.content_length, 45)
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual('%r' % (test_app,), "wsgify(tests.test_dec.test_app, "
+ "RequestClass=<class 'tests.test_dec.MyRequest'>)")
+
+ def test_middleware(self):
+ resp_str = "These are the vars: %s"
+ @wsgify.middleware
+ def set_urlvar(req, app, **vars):
+ req.urlvars.update(vars)
+ return app(req)
+ from webob.dec import _MiddlewareFactory
+ self.assert_(set_urlvar.__class__ is _MiddlewareFactory)
+ repr = '%r' % (set_urlvar,)
+ self.assert_(repr.startswith('wsgify.middleware(<function set_urlvar at '))
+ @wsgify
+ def show_vars(req):
+ return resp_str % (sorted(req.urlvars.items()))
+ show_vars2 = set_urlvar(show_vars, a=1, b=2)
+ self.assertEqual('%r' % (show_vars2,),
+ 'wsgify.middleware(tests.test_dec.set_urlvar)'
+ '(wsgify(tests.test_dec.show_vars), a=1, b=2)')
+ resp = self._testit(show_vars2, '/path')
+ self.assertEqual(resp.body, resp_str % "[('a', 1), ('b', 2)]")
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual(resp.content_length, 40)
+
+ def test_unbound_middleware(self):
+ @wsgify
+ def test_app(req):
+ return Response('Say wha!?')
+ unbound = wsgify.middleware(None, test_app, some='thing')
+ from webob.dec import _UnboundMiddleware
+ self.assert_(unbound.__class__ is _UnboundMiddleware)
+ self.assertEqual(unbound.kw, dict(some='thing'))
+ self.assertEqual('%r' % (unbound,),
+ "wsgify.middleware(wsgify(tests.test_dec.test_app), "
+ "some='thing')")
+ @unbound
+ def middle(req, app, **kw):
+ return app(req)
+ self.assert_(middle.__class__ is wsgify)
+ self.assertEqual('%r' % (middle,),
+ "wsgify.middleware(tests.test_dec.middle)"
+ "(wsgify(tests.test_dec.test_app), some='thing')")
+
+ def test_unbound_middleware_no_app(self):
+ unbound = wsgify.middleware(None, None)
+ from webob.dec import _UnboundMiddleware
+ self.assert_(unbound.__class__ is _UnboundMiddleware)
+ self.assertEqual(unbound.kw, dict())
+ self.assertEqual('%r' % (unbound,),
+ "wsgify.middleware()")
+
+ def test_classapp(self):
+ class HostMap(dict):
+ @wsgify
+ def __call__(self, req):
+ return self[req.host.split(':')[0]]
+ app = HostMap()
+ app['example.com'] = Response('1')
+ app['other.com'] = Response('2')
+ resp = Request.blank('http://example.com/').get_response(wsgify(app))
+ self.assertEqual(resp.content_type, 'text/html')
+ self.assertEqual(resp.charset, 'UTF-8')
+ self.assertEqual(resp.content_length, 1)
+ self.assertEqual(resp.body, '1')
+
+ def test__func_name(self):
+ def func():
+ pass
+ name = _func_name(func)
+ self.assertEqual(name, 'tests.test_dec.func')
+ name = _func_name('a')
+ self.assertEqual(name, "'a'")
+ class Klass(object):
+ @classmethod
+ def classmeth(cls):
+ pass
+ def meth(self):
+ pass
+ name = _func_name(Klass)
+ self.assertEqual(name, 'tests.test_dec.Klass')
+ k = Klass()
+ kname = _func_name(k)
+ self.assert_(kname.startswith('<tests.test_dec.Klass object at 0x'))
+ name = _func_name(k.meth)
+ self.assert_(name.startswith('tests.test_dec.%s' % kname))
+ self.assert_(name.endswith('>.meth'))
+ name = _func_name(Klass.meth)
+ self.assertEqual(name, 'tests.test_dec.Klass.meth')
+ name = _func_name(Klass.classmeth)
+ self.assertEqual(name, "tests.test_dec.<class "
+ "'tests.test_dec.Klass'>.classmeth")
+
+ def test__format_args(self):
+ args_rep = _format_args()
+ self.assertEqual(args_rep, '')
+ kw = dict(a=4, b=5, c=6)
+ args_rep = _format_args(args=(1, 2, 3), kw=kw)
+ self.assertEqual(args_rep, '1, 2, 3, a=4, b=5, c=6')
+ args_rep = _format_args(args=(1, 2, 3), kw=kw, leading_comma=True)
+ self.assertEqual(args_rep, ', 1, 2, 3, a=4, b=5, c=6')
+ class Klass(object):
+ a = 1
+ b = 2
+ c = 3
+ names = ['a', 'b', 'c']
+ obj = Klass()
+ self.assertRaises(AssertionError, _format_args, names=names)
+ args_rep = _format_args(obj=obj, names='a')
+ self.assertEqual(args_rep, 'a=1')
+ args_rep = _format_args(obj=obj, names=names)
+ self.assertEqual(args_rep, 'a=1, b=2, c=3')
+ args_rep = _format_args(kw=kw, defaults=dict(a=4, b=5))
+ self.assertEqual(args_rep, 'c=6')
diff --git a/tests/test_descriptors.py b/tests/test_descriptors.py
new file mode 100644
index 0000000..9329068
--- /dev/null
+++ b/tests/test_descriptors.py
@@ -0,0 +1,797 @@
+# -*- coding: utf-8 -*-
+
+from datetime import tzinfo
+from datetime import timedelta
+
+from nose.tools import eq_
+from nose.tools import ok_
+from nose.tools import assert_raises
+
+from webob import Request
+
+
+class GMT(tzinfo):
+ """UTC"""
+ ZERO = timedelta(0)
+ def utcoffset(self, dt):
+ return self.ZERO
+
+ def tzname(self, dt):
+ return "UTC"
+
+ def dst(self, dt):
+ return self.ZERO
+
+
+class MockDescriptor:
+ _val = 'avalue'
+ def __get__(self, obj, type=None):
+ return self._val
+ def __set__(self, obj, val):
+ self._val = val
+ def __delete__(self, obj):
+ self._val = None
+
+
+def test_environ_getter_docstring():
+ from webob.descriptors import environ_getter
+ desc = environ_getter('akey')
+ eq_(desc.__doc__, "Gets and sets the 'akey' key in the environment.")
+
+def test_environ_getter_nodefault_keyerror():
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ desc = environ_getter('akey')
+ assert_raises(KeyError, desc.fget, req)
+
+def test_environ_getter_nodefault_fget():
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ desc = environ_getter('akey')
+ desc.fset(req, 'bar')
+ eq_(req.environ['akey'], 'bar')
+
+def test_environ_getter_nodefault_fdel():
+ from webob.descriptors import environ_getter
+ desc = environ_getter('akey')
+ eq_(desc.fdel, None)
+
+def test_environ_getter_default_fget():
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ desc = environ_getter('akey', default='the_default')
+ eq_(desc.fget(req), 'the_default')
+
+def test_environ_getter_default_fset():
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ desc = environ_getter('akey', default='the_default')
+ desc.fset(req, 'bar')
+ eq_(req.environ['akey'], 'bar')
+
+def test_environ_getter_default_fset_none():
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ desc = environ_getter('akey', default='the_default')
+ desc.fset(req, 'baz')
+ desc.fset(req, None)
+ ok_('akey' not in req.environ)
+
+def test_environ_getter_default_fdel():
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ desc = environ_getter('akey', default='the_default')
+ desc.fset(req, 'baz')
+ assert 'akey' in req.environ
+ desc.fdel(req)
+ ok_('akey' not in req.environ)
+
+def test_environ_getter_rfc_section():
+ from webob.descriptors import environ_getter
+ desc = environ_getter('akey', rfc_section='14.3')
+ eq_(desc.__doc__, "Gets and sets the 'akey' key in the environment. For "
+ "more information on akey see `section 14.3 "
+ "<http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3>`_.")
+
+def test_upath_property_fget():
+ from webob.descriptors import upath_property
+ req = Request.blank('/')
+ desc = upath_property('akey')
+ eq_(desc.fget(req), '')
+
+def test_upath_property_fset():
+ from webob.descriptors import upath_property
+ req = Request.blank('/')
+ desc = upath_property('akey')
+ desc.fset(req, 'avalue')
+ eq_(desc.fget(req), 'avalue')
+
+def test_header_getter_doc():
+ from webob.descriptors import header_getter
+ desc = header_getter('AHEADER', '14.3')
+ eq_(desc.__doc__, "Gets and sets and deletes the AHEADER header. For "
+ "more information on AHEADER see `section 14.3 "
+ "<http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3>`_.")
+
+def test_header_getter_fget():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ eq_(desc.fget(resp), None)
+
+def test_header_getter_fset():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, 'avalue')
+ eq_(desc.fget(resp), 'avalue')
+
+def test_header_getter_fset_none():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, 'avalue')
+ desc.fset(resp, None)
+ eq_(desc.fget(resp), None)
+
+def test_header_getter_fdel():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, 'avalue2')
+ desc.fdel(resp)
+ eq_(desc.fget(resp), None)
+
+def test_header_getter_unicode():
+ from webob.descriptors import header_getter
+ desc = header_getter('AHEADER', '14.3')
+ eq_(desc.__doc__, "Gets and sets and deletes the AHEADER header. For "
+ "more information on AHEADER see `section 14.3 "
+ "<http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3>`_.")
+
+def test_header_getter_unicode_fget_none():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ eq_(desc.fget(resp), None)
+
+def test_header_getter_unicode_fget():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, u'avalue')
+ eq_(desc.fget(resp), u'avalue')
+
+def test_header_getter_unicode_fset_none():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, None)
+ eq_(desc.fget(resp), None)
+
+def test_header_getter_unicode_fset():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, u'avalue2')
+ eq_(desc.fget(resp), u'avalue2')
+
+def test_header_getter_unicode_fdel():
+ from webob.descriptors import header_getter
+ from webob import Response
+ resp = Response('aresp')
+ desc = header_getter('AHEADER', '14.3')
+ desc.fset(resp, u'avalue3')
+ desc.fdel(resp)
+ eq_(desc.fget(resp), None)
+
+def test_converter_not_prop():
+ from webob.descriptors import converter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ assert_raises(AssertionError,converter,
+ ('CONTENT_LENGTH', None, '14.13'),
+ parse_int_safe, serialize_int, 'int')
+
+def test_converter_with_name_docstring():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ desc = converter(
+ environ_getter('CONTENT_LENGTH', '666', '14.13'),
+ parse_int_safe, serialize_int, 'int')
+ eq_(desc.__doc__, "Gets and sets the 'CONTENT_LENGTH' key in the "
+ "environment. For more information on CONTENT_LENGTH see `section 14.13 "
+ "<http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13>`_. "
+ "Converts it using int.")
+
+def test_converter_with_name_fget():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ req = Request.blank('/')
+ desc = converter(
+ environ_getter('CONTENT_LENGTH', '666', '14.13'),
+ parse_int_safe, serialize_int, 'int')
+ eq_(desc.fget(req), 666)
+
+def test_converter_with_name_fset():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ req = Request.blank('/')
+ desc = converter(
+ environ_getter('CONTENT_LENGTH', '666', '14.13'),
+ parse_int_safe, serialize_int, 'int')
+ desc.fset(req, '999')
+ eq_(desc.fget(req), 999)
+
+def test_converter_without_name_fget():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ req = Request.blank('/')
+ desc = converter(
+ environ_getter('CONTENT_LENGTH', '666', '14.13'),
+ parse_int_safe, serialize_int)
+ eq_(desc.fget(req), 666)
+
+def test_converter_without_name_fset():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ req = Request.blank('/')
+ desc = converter(
+ environ_getter('CONTENT_LENGTH', '666', '14.13'),
+ parse_int_safe, serialize_int)
+ desc.fset(req, '999')
+ eq_(desc.fget(req), 999)
+
+def test_converter_none_for_wrong_type():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ req = Request.blank('/')
+ desc = converter(
+ ## XXX: Should this fail if the type is wrong?
+ environ_getter('CONTENT_LENGTH', 'sixsixsix', '14.13'),
+ parse_int_safe, serialize_int, 'int')
+ eq_(desc.fget(req), None)
+
+def test_converter_delete():
+ from webob.descriptors import converter
+ from webob.descriptors import environ_getter
+ from webob.descriptors import parse_int_safe
+ from webob.descriptors import serialize_int
+ req = Request.blank('/')
+ desc = converter(
+ ## XXX: Should this fail if the type is wrong?
+ environ_getter('CONTENT_LENGTH', '666', '14.13'),
+ parse_int_safe, serialize_int, 'int')
+ assert_raises(KeyError, desc.fdel, req)
+
+def test_list_header():
+ from webob.descriptors import list_header
+ desc = list_header('CONTENT_LENGTH', '14.13')
+ eq_(type(desc), property)
+
+def test_parse_list_single():
+ from webob.descriptors import parse_list
+ result = parse_list('avalue')
+ eq_(result, ('avalue',))
+
+def test_parse_list_multiple():
+ from webob.descriptors import parse_list
+ result = parse_list('avalue,avalue2')
+ eq_(result, ('avalue', 'avalue2'))
+
+def test_parse_list_none():
+ from webob.descriptors import parse_list
+ result = parse_list(None)
+ eq_(result, None)
+
+def test_parse_list_unicode_single():
+ from webob.descriptors import parse_list
+ result = parse_list(u'avalue')
+ eq_(result, ('avalue',))
+
+def test_parse_list_unicode_multiple():
+ from webob.descriptors import parse_list
+ result = parse_list(u'avalue,avalue2')
+ eq_(result, ('avalue', 'avalue2'))
+
+def test_serialize_list():
+ from webob.descriptors import serialize_list
+ result = serialize_list(('avalue', 'avalue2'))
+ eq_(result, 'avalue, avalue2')
+
+def test_serialize_list_string():
+ from webob.descriptors import serialize_list
+ result = serialize_list('avalue')
+ eq_(result, 'avalue')
+
+def test_serialize_list_unicode():
+ from webob.descriptors import serialize_list
+ result = serialize_list(u'avalue')
+ eq_(result, u'avalue')
+
+def test_converter_date():
+ import datetime
+ from webob.descriptors import converter_date
+ from webob.descriptors import environ_getter
+ req = Request.blank('/')
+ UTC = GMT()
+ desc = converter_date(environ_getter(
+ "HTTP_DATE", "Tue, 15 Nov 1994 08:12:31 GMT", "14.8"))
+ eq_(desc.fget(req),
+ datetime.datetime(1994, 11, 15, 8, 12, 31, tzinfo=UTC))
+
+def test_converter_date_docstring():
+ from webob.descriptors import converter_date
+ from webob.descriptors import environ_getter
+ desc = converter_date(environ_getter(
+ "HTTP_DATE", "Tue, 15 Nov 1994 08:12:31 GMT", "14.8"))
+ eq_(desc.__doc__, "Gets and sets the 'HTTP_DATE' key in the environment. "
+ "For more information on Date see `section 14.8 "
+ "<http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.8>`_. "
+ "Converts it using HTTP date.")
+
+def test_date_header_fget_none():
+ from webob import Response
+ from webob.descriptors import date_header
+ resp = Response('aresponse')
+ desc = date_header('HTTP_DATE', "14.8")
+ eq_(desc.fget(resp), None)
+
+def test_date_header_fset_fget():
+ import datetime
+ from webob import Response
+ from webob.descriptors import date_header
+ resp = Response('aresponse')
+ UTC = GMT()
+ desc = date_header('HTTP_DATE', "14.8")
+ desc.fset(resp, "Tue, 15 Nov 1994 08:12:31 GMT")
+ eq_(desc.fget(resp), datetime.datetime(1994, 11, 15, 8, 12, 31, tzinfo=UTC))
+
+def test_date_header_fdel():
+ from webob import Response
+ from webob.descriptors import date_header
+ resp = Response('aresponse')
+ desc = date_header('HTTP_DATE', "14.8")
+ desc.fset(resp, "Tue, 15 Nov 1994 08:12:31 GMT")
+ desc.fdel(resp)
+ eq_(desc.fget(resp), None)
+
+def test_deprecated_property_ctor_prop():
+ from webob.descriptors import deprecated_property
+ prop = property()
+ dep = deprecated_property(prop,
+ 'deprecated_property',
+ "Don't use it",
+ warning=False)
+ eq_(dep.descriptor, prop)
+
+def test_deprecated_property_ctor_attr():
+ from webob.descriptors import deprecated_property
+ prop = property()
+ dep = deprecated_property(prop,
+ 'deprecated_property',
+ "Don't use it",
+ warning=False)
+ eq_(dep.attr, 'deprecated_property')
+
+def test_deprecated_property_ctor_message():
+ from webob.descriptors import deprecated_property
+ prop = property()
+ dep = deprecated_property(prop,
+ 'deprecated_property',
+ "Don't use it",
+ warning=False)
+ eq_(dep.message, "Don't use it")
+
+def test_deprecated_property_ctor_raises():
+ from webob.descriptors import deprecated_property
+ prop = property()
+ dep = deprecated_property(prop,
+ 'deprecated_property',
+ "Don't use it",
+ warning=False)
+ assert_raises(DeprecationWarning, dep.warn)
+
+def test_deprecated_property_get():
+ from webob.descriptors import deprecated_property
+ dep = deprecated_property(deprecated_property,
+ 'deprecated_property',
+ 'DEPRECATED',
+ warning=False)
+ assert_raises(DeprecationWarning, dep.__get__, dep)
+
+def test_deprecated_property_get_none():
+ from webob.descriptors import deprecated_property
+ dep = deprecated_property(None,
+ 'deprecated_property',
+ 'DEPRECATED',
+ warning=False)
+ eq_(dep.__get__(None), dep)
+
+def test_deprecated_property_set():
+ from webob.descriptors import deprecated_property
+ dep = deprecated_property(deprecated_property,
+ 'deprecated_property',
+ 'DEPRECATED',
+ warning=False)
+ assert_raises(DeprecationWarning, dep.__set__, dep, 'avalue')
+
+def test_deprecated_property_delete():
+ from webob.descriptors import deprecated_property
+ dep = deprecated_property(deprecated_property,
+ 'deprecated_property',
+ 'DEPRECATED',
+ warning=False)
+ assert_raises(DeprecationWarning, dep.__delete__, dep)
+
+def test_deprecated_property_repr():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ try:
+ warnings.simplefilter('ignore')
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ assert dep.__repr__().startswith(
+ "<Deprecated attribute mock_property: "
+ "<tests.test_descriptors.MockDescriptor instance at")
+ finally:
+ warnings.resetwarnings()
+
+def test_deprecated_property_warn_get():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ try:
+ warnings.simplefilter('error')
+ assert_raises(DeprecationWarning, dep.__get__, mock)
+ finally:
+ warnings.resetwarnings()
+
+def test_deprecated_property_warn_set():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ try:
+ warnings.simplefilter('error')
+ assert_raises(DeprecationWarning, dep.__set__, mock, 'avalue')
+ finally:
+ warnings.resetwarnings()
+
+def test_deprecated_property_warn_delete():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ try:
+ warnings.simplefilter('error')
+ assert_raises(DeprecationWarning, dep.__delete__, mock)
+ finally:
+ warnings.resetwarnings()
+
+def test_deprecated_property_warn_get_call():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ try:
+ warnings.simplefilter('ignore')
+ eq_(dep.__get__(mock), 'avalue')
+ finally:
+ warnings.resetwarnings()
+
+def test_deprecated_property_warn_set_call():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ try:
+ warnings.simplefilter('ignore')
+ dep.__set__(mock, 'avalue2')
+ eq_(dep.__get__(mock), 'avalue2')
+ finally:
+ warnings.resetwarnings()
+
+def test_deprecated_property_warn_delete_call():
+ import warnings
+ from webob.descriptors import deprecated_property
+ mock = MockDescriptor()
+ dep = deprecated_property(mock,
+ 'mock_property',
+ 'DEPRECATED')
+ try:
+ warnings.simplefilter('ignore')
+ dep.__delete__(mock)
+ eq_(dep.__get__(mock), None)
+ finally:
+ warnings.resetwarnings()
+
+def test_parse_etag_response():
+ from webob.descriptors import parse_etag_response
+ etresp = parse_etag_response("etag")
+ eq_(etresp, "etag")
+
+def test_parse_etag_response_quoted():
+ from webob.descriptors import parse_etag_response
+ etresp = parse_etag_response('"etag"')
+ eq_(etresp, "etag")
+
+def test_parse_etag_response_is_none():
+ from webob.descriptors import parse_etag_response
+ etresp = parse_etag_response(None)
+ eq_(etresp, None)
+
+def test_serialize_etag_response():
+ from webob.descriptors import serialize_etag_response
+ etresp = serialize_etag_response("etag")
+ eq_(etresp, '"etag"')
+
+def test_parse_if_range_is_None():
+ from webob.descriptors import parse_if_range
+ from webob.descriptors import NoIfRange
+ eq_(NoIfRange, parse_if_range(None))
+
+def test_parse_if_range_date_ifr():
+ from webob.descriptors import parse_if_range
+ from webob.descriptors import IfRange
+ ifr = parse_if_range("2011-03-15 01:24:43.272409")
+ eq_(type(ifr), IfRange)
+
+def test_parse_if_range_date_etagmatcher():
+ from webob.descriptors import parse_if_range
+ from webob.etag import ETagMatcher
+ ifr = parse_if_range("2011-03-15 01:24:43.272409")
+ eq_(type(ifr.etag), ETagMatcher)
+
+def test_serialize_if_range_string():
+ from webob.descriptors import serialize_if_range
+ val = serialize_if_range("avalue")
+ eq_(val, "avalue")
+
+def test_serialize_if_range_unicode():
+ from webob.descriptors import serialize_if_range
+ val = serialize_if_range(u"avalue")
+ eq_(val, u"avalue")
+
+def test_serialize_if_range_datetime():
+ import datetime
+ from webob.descriptors import serialize_if_range
+ UTC = GMT()
+ val = serialize_if_range(datetime.datetime(1994, 11, 15, 8, 12, 31, tzinfo=UTC))
+ eq_(val, "Tue, 15 Nov 1994 08:12:31 GMT")
+
+def test_serialize_if_range_other():
+ from webob.descriptors import serialize_if_range
+ val = serialize_if_range(123456)
+ eq_(val, '123456')
+
+def test_parse_range_none():
+ from webob.descriptors import parse_range
+ val = parse_range(None)
+ eq_(val, None)
+
+def test_parse_range_type():
+ from webob.byterange import Range
+ from webob.descriptors import parse_range
+ val = parse_range("bytes=1-500")
+ eq_(type(val), type(Range.parse("bytes=1-500")))
+
+def test_parse_range_values():
+ from webob.byterange import Range
+ from webob.descriptors import parse_range
+ val = parse_range("bytes=1-500")
+ eq_(val.ranges, Range.parse("bytes=1-500").ranges)
+
+def test_serialize_range_none():
+ from webob.descriptors import serialize_range
+ val = serialize_range(None)
+ eq_(val, None)
+
+def test_serialize_range():
+ from webob.descriptors import serialize_range
+ val = serialize_range((1,500))
+ eq_(val, 'bytes=1-499')
+
+def test_serialize_invalid_len():
+ from webob.descriptors import serialize_range
+ assert_raises(ValueError, serialize_range, (1,))
+
+def test_parse_int_none():
+ from webob.descriptors import parse_int
+ val = parse_int(None)
+ eq_(val, None)
+
+def test_parse_int_emptystr():
+ from webob.descriptors import parse_int
+ val = parse_int('')
+ eq_(val, None)
+
+def test_parse_int():
+ from webob.descriptors import parse_int
+ val = parse_int('123')
+ eq_(val, 123)
+
+def test_parse_int_invalid():
+ from webob.descriptors import parse_int
+ assert_raises(ValueError, parse_int, 'abc')
+
+def test_parse_int_safe_none():
+ from webob.descriptors import parse_int_safe
+ eq_(parse_int_safe(None), None)
+
+def test_parse_int_safe_emptystr():
+ from webob.descriptors import parse_int_safe
+ eq_(parse_int_safe(''), None)
+
+def test_parse_int_safe():
+ from webob.descriptors import parse_int_safe
+ eq_(parse_int_safe('123'), 123)
+
+def test_parse_int_safe_invalid():
+ from webob.descriptors import parse_int_safe
+ eq_(parse_int_safe('abc'), None)
+
+def test_serialize_int():
+ from webob.descriptors import serialize_int
+ assert serialize_int is str
+
+def test_parse_content_range_none():
+ from webob.descriptors import parse_content_range
+ eq_(parse_content_range(None), None)
+
+def test_parse_content_range_emptystr():
+ from webob.descriptors import parse_content_range
+ eq_(parse_content_range(' '), None)
+
+def test_parse_content_range_length():
+ from webob.byterange import ContentRange
+ from webob.descriptors import parse_content_range
+ val = parse_content_range("bytes 0-499/1234")
+ eq_(val.length, ContentRange.parse("bytes 0-499/1234").length)
+
+def test_parse_content_range_start():
+ from webob.byterange import ContentRange
+ from webob.descriptors import parse_content_range
+ val = parse_content_range("bytes 0-499/1234")
+ eq_(val.start, ContentRange.parse("bytes 0-499/1234").start)
+
+def test_parse_content_range_stop():
+ from webob.byterange import ContentRange
+ from webob.descriptors import parse_content_range
+ val = parse_content_range("bytes 0-499/1234")
+ eq_(val.stop, ContentRange.parse("bytes 0-499/1234").stop)
+
+def test_serialize_content_range_none():
+ from webob.descriptors import serialize_content_range
+ eq_(serialize_content_range(None), 'None') ### XXX: Seems wrong
+
+def test_serialize_content_range_emptystr():
+ from webob.descriptors import serialize_content_range
+ eq_(serialize_content_range(''), None)
+
+def test_serialize_content_range_invalid():
+ from webob.descriptors import serialize_content_range
+ assert_raises(ValueError, serialize_content_range, (1,))
+
+def test_serialize_content_range_asterisk():
+ from webob.descriptors import serialize_content_range
+ eq_(serialize_content_range((0, 500)), 'bytes 0-499/*')
+
+def test_serialize_content_range_defined():
+ from webob.descriptors import serialize_content_range
+ eq_(serialize_content_range((0, 500, 1234)), 'bytes 0-499/1234')
+
+def test_parse_auth_params_leading_capital_letter():
+ from webob.descriptors import parse_auth_params
+ val = parse_auth_params('Basic Realm=WebOb')
+ eq_(val, {'ealm': 'WebOb'})
+
+def test_parse_auth_params_trailing_capital_letter():
+ from webob.descriptors import parse_auth_params
+ val = parse_auth_params('Basic realM=WebOb')
+ eq_(val, {})
+
+def test_parse_auth_params_doublequotes():
+ from webob.descriptors import parse_auth_params
+ val = parse_auth_params('Basic realm="Web Object"')
+ eq_(val, {'realm': 'Web Object'})
+
+def test_parse_auth_params_multiple_values():
+ from webob.descriptors import parse_auth_params
+ val = parse_auth_params("foo='blah &&234', qop=foo, nonce='qwerty1234'")
+ eq_(val, {'nonce': "'qwerty1234'", 'foo': "'blah &&234'", 'qop': 'foo'})
+
+def test_parse_auth_params_truncate_on_comma():
+ from webob.descriptors import parse_auth_params
+ val = parse_auth_params("Basic realm=WebOb,this_will_truncate")
+ eq_(val, {'realm': 'WebOb'})
+
+def test_parse_auth_params_emptystr():
+ from webob.descriptors import parse_auth_params
+ eq_(parse_auth_params(''), {})
+
+def test_parse_auth_none():
+ from webob.descriptors import parse_auth
+ eq_(parse_auth(None), None)
+
+def test_parse_auth_emptystr():
+ from webob.descriptors import parse_auth
+ assert_raises(ValueError, parse_auth, '')
+
+def test_parse_auth_basic():
+ from webob.descriptors import parse_auth
+ eq_(parse_auth("Basic realm=WebOb"), ('Basic', 'realm=WebOb'))
+
+def test_parse_auth_basic_quoted():
+ from webob.descriptors import parse_auth
+ eq_(parse_auth('Basic realm="Web Ob"'), ('Basic', {'realm': 'Web Ob'}))
+
+def test_parse_auth_basic_quoted_multiple_unknown():
+ from webob.descriptors import parse_auth
+ eq_(parse_auth("foo='blah &&234', qop=foo, nonce='qwerty1234'"),
+ ("foo='blah", "&&234', qop=foo, nonce='qwerty1234'"))
+
+def test_parse_auth_basic_quoted_known_multiple():
+ from webob.descriptors import parse_auth
+ eq_(parse_auth("Basic realm='blah &&234', qop=foo, nonce='qwerty1234'"),
+ ('Basic', "realm='blah &&234', qop=foo, nonce='qwerty1234'"))
+
+def test_serialize_auth_none():
+ from webob.descriptors import serialize_auth
+ eq_(serialize_auth(None), None)
+
+def test_serialize_auth_emptystr():
+ from webob.descriptors import serialize_auth
+ eq_(serialize_auth(''), '')
+
+def test_serialize_auth_basic_quoted():
+ from webob.descriptors import serialize_auth
+ val = serialize_auth(('Basic', 'realm="WebOb"'))
+ eq_(val, 'Basic realm="WebOb"')
+
+def test_serialize_auth_digest_multiple():
+ from webob.descriptors import serialize_auth
+ val = serialize_auth(('Digest', 'realm="WebOb", nonce=abcde12345, qop=foo'))
+ flags = val[len('Digest'):]
+ result = sorted([ x.strip() for x in flags.split(',') ])
+ eq_(result, ['nonce=abcde12345', 'qop=foo', 'realm="WebOb"'])
+
+def test_serialize_auth_digest_tuple():
+ from webob.descriptors import serialize_auth
+ val = serialize_auth(('Digest', {'realm':'"WebOb"', 'nonce':'abcde12345', 'qop':'foo'}))
+ flags = val[len('Digest'):]
+ result = sorted([ x.strip() for x in flags.split(',') ])
+ eq_(result, ['nonce="abcde12345"', 'qop="foo"', 'realm=""WebOb""'])
diff --git a/tests/test_etag.py b/tests/test_etag.py
new file mode 100644
index 0000000..ab182e4
--- /dev/null
+++ b/tests/test_etag.py
@@ -0,0 +1,451 @@
+import unittest
+
+class etag_propertyTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from webob.etag import etag_property
+ return etag_property
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def _makeDummyRequest(self, **kw):
+ """
+ Return a DummyRequest object with attrs from kwargs.
+ Use like: dr = _makeDummyRequest(environment={'userid': 'johngalt'})
+ Then you can: uid = dr.environment.get('userid', 'SomeDefault')
+ """
+ class Dummy(object):
+ def __init__(self, **kwargs):
+ self.__dict__.update(**kwargs)
+ d = Dummy(**kw)
+ return d
+
+ def test_fget_missing_key(self):
+ ep = self._makeOne("KEY", "DEFAULT", "RFC_SECTION")
+ req = self._makeDummyRequest(environ={})
+ self.assertEquals(ep.fget(req), "DEFAULT")
+
+ def test_fget_found_key(self):
+ ep = self._makeOne("KEY", "DEFAULT", "RFC_SECTION")
+ req = self._makeDummyRequest(environ={'KEY':'VALUE'})
+ res = ep.fget(req)
+ self.assertEquals(res.etags, ['VALUE'])
+ self.assertEquals(res.weak_etags, [])
+
+ def test_fget_star_key(self):
+ ep = self._makeOne("KEY", "DEFAULT", "RFC_SECTION")
+ req = self._makeDummyRequest(environ={'KEY':'*'})
+ res = ep.fget(req)
+ import webob.etag
+ self.assertEquals(type(res), webob.etag._AnyETag)
+ self.assertEquals(res.__dict__, {})
+
+ def test_fset_None(self):
+ ep = self._makeOne("KEY", "DEFAULT", "RFC_SECTION")
+ req = self._makeDummyRequest(environ={'KEY':'*'})
+ res = ep.fset(req, None)
+ self.assertEquals(res, None)
+
+ def test_fset_not_None(self):
+ ep = self._makeOne("KEY", "DEFAULT", "RFC_SECTION")
+ req = self._makeDummyRequest(environ={'KEY':'OLDVAL'})
+ res = ep.fset(req, "NEWVAL")
+ self.assertEquals(res, None)
+ self.assertEquals(req.environ['KEY'], 'NEWVAL')
+
+ def test_fedl(self):
+ ep = self._makeOne("KEY", "DEFAULT", "RFC_SECTION")
+ req = self._makeDummyRequest(environ={'KEY':'VAL', 'QUAY':'VALYOU'})
+ res = ep.fdel(req)
+ self.assertEquals(res, None)
+ self.assertFalse('KEY' in req.environ)
+ self.assertEquals(req.environ['QUAY'], 'VALYOU')
+
+class AnyETagTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from webob.etag import _AnyETag
+ return _AnyETag
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def test___repr__(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__repr__(), '<ETag *>')
+
+ def test___nonzero__(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__nonzero__(), False)
+
+ def test___contains__None(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__(None), True)
+
+ def test___contains__empty_list(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__([]), True)
+
+ def test___contains__empty_string(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__(''), True)
+
+ def test___contains__something(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__('something'), True)
+
+ def test_weak_match_None(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match(None), True)
+
+ def test_weak_match_empty_list(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match([]), True)
+
+ def test_weak_match_empty_string(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match(''), True)
+
+ def test_weak_match_something(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match('something'), True)
+
+ def test___str__(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__str__(), '*')
+
+class NoETagTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from webob.etag import _NoETag
+ return _NoETag
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def test___repr__(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__repr__(), '<No ETag>')
+
+ def test___nonzero__(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__nonzero__(), False)
+
+ def test___contains__None(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__(None), False)
+
+ def test___contains__empty_list(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__([]), False)
+
+ def test___contains__empty_string(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__(''), False)
+
+ def test___contains__something(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__contains__('something'), False)
+
+ def test_weak_match_None(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match(None), False)
+
+ def test_weak_match_empty_list(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match([]), False)
+
+ def test_weak_match_empty_string(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match(''), False)
+
+ def test_weak_match_something(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.weak_match('something'), False)
+
+ def test___str__(self):
+ etag = self._makeOne()
+ self.assertEqual(etag.__str__(), '')
+
+class ETagMatcherTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from webob.etag import ETagMatcher
+ return ETagMatcher
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def test___init__wo_weak_etags(self):
+ matcher = self._makeOne(("ETAGS",))
+ self.assertEqual(matcher.etags, ("ETAGS",))
+ self.assertEqual(matcher.weak_etags, ())
+
+ def test___init__w_weak_etags(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertEqual(matcher.etags, ("ETAGS",))
+ self.assertEqual(matcher.weak_etags, ("WEAK",))
+
+ def test___contains__tags(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertTrue("ETAGS" in matcher)
+
+ def test___contains__weak_tags(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertTrue("WEAK" in matcher)
+
+ def test___contains__not(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertFalse("BEER" in matcher)
+
+ def test___contains__None(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertFalse(None in matcher)
+
+ def test_weak_match_etags(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertTrue(matcher.weak_match("W/ETAGS"))
+
+ def test_weak_match_weak_etags(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertTrue(matcher.weak_match("W/WEAK"))
+
+ def test_weak_match_weak_not(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertFalse(matcher.weak_match("W/BEER"))
+
+ def test_weak_match_weak_wo_wslash(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertTrue(matcher.weak_match("ETAGS"))
+
+ def test_weak_match_weak_wo_wslash_not(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertFalse(matcher.weak_match("BEER"))
+
+ def test___repr__one(self):
+ matcher = self._makeOne(("ETAGS",), ("WEAK",))
+ self.assertEqual(matcher.__repr__(), '<ETag ETAGS>')
+
+ def test___repr__multi(self):
+ matcher = self._makeOne(("ETAG1","ETAG2"), ("WEAK",))
+ self.assertEqual(matcher.__repr__(), '<ETag ETAG1 or ETAG2>')
+
+ def test_parse_None(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse(None)
+ self.assertEqual(et.etags, [])
+ self.assertEqual(et.weak_etags, [])
+
+ def test_parse_anyetag(self):
+ # these tests smell bad, are they useful?
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('*')
+ self.assertEqual(et.__dict__, {})
+ self.assertEqual(et.__repr__(), '<ETag *>')
+
+ def test_parse_one(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('ONE')
+ self.assertEqual(et.etags, ['ONE'])
+ self.assertEqual(et.weak_etags, [])
+
+ # .parse doesn't use the contructor values (etags [ ,weak_etags])
+
+ def test_parse_commasep(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('ONE, TWO')
+ self.assertEqual(et.etags, ['ONE', 'TWO'])
+ self.assertEqual(et.weak_etags, [])
+
+ def test_parse_commasep_w_weak(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('ONE, w/TWO')
+ self.assertEqual(et.etags, ['ONE'])
+ self.assertEqual(et.weak_etags, ['TWO'])
+
+ def test_parse_quoted(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('"ONE"')
+ self.assertEqual(et.etags, ['ONE'])
+ self.assertEqual(et.weak_etags, [])
+
+ def test_parse_quoted_two(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('"ONE", "TWO"')
+ self.assertEqual(et.etags, ['ONE', 'TWO'])
+ self.assertEqual(et.weak_etags, [])
+
+ def test_parse_quoted_two_weak(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('"ONE", W/"TWO"')
+ self.assertEqual(et.etags, ['ONE'])
+ self.assertEqual(et.weak_etags, ['TWO'])
+
+ def test_parse_wo_close_quote(self):
+ # Unsure if this is testing likely input
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ et = matcher.parse('"ONE')
+ self.assertEqual(et.etags, ['ONE'])
+ self.assertEqual(et.weak_etags, [])
+
+ def test___str__etag(self):
+ matcher = self._makeOne(("ETAG",))
+ self.assertEqual(matcher.__str__(), 'ETAG')
+
+ def test___str__etag_w_weak(self):
+ matcher = self._makeOne(("ETAG",), ("WEAK",))
+ self.assertEqual(matcher.__str__(), 'ETAG, W/WEAK')
+
+class IfRangeTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from webob.etag import IfRange
+ return IfRange
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def test___init__(self):
+ ir = self._makeOne()
+ self.assertEqual(ir.etag, None)
+ self.assertEqual(ir.date, None)
+
+ def test___init__etag(self):
+ ir = self._makeOne(etag='ETAG')
+ self.assertEqual(ir.etag, 'ETAG')
+ self.assertEqual(ir.date, None)
+
+ def test___init__date(self):
+ ir = self._makeOne(date='DATE')
+ self.assertEqual(ir.etag, None)
+ self.assertEqual(ir.date, 'DATE')
+
+ def test___init__etag_date(self):
+ ir = self._makeOne(etag='ETAG', date='DATE')
+ self.assertEqual(ir.etag, 'ETAG')
+ self.assertEqual(ir.date, 'DATE')
+
+ def test___repr__(self):
+ ir = self._makeOne()
+ self.assertEqual(ir.__repr__(), '<IfRange etag=*, date=*>')
+
+ def test___repr__etag(self):
+ ir = self._makeOne(etag='ETAG')
+ self.assertEqual(ir.__repr__(), '<IfRange etag=ETAG, date=*>')
+
+ def test___repr__date(self):
+ ir = self._makeOne(date='Fri, 09 Nov 2001 01:08:47 -0000')
+ self.assertEqual(ir.__repr__(),
+ '<IfRange etag=*, ' +
+ 'date=Fri, 09 Nov 2001 01:08:47 -0000>')
+
+ def test___repr__etag_date(self):
+ ir = self._makeOne(etag='ETAG', date='Fri, 09 Nov 2001 01:08:47 -0000')
+ self.assertEqual(ir.__repr__(),
+ '<IfRange etag=ETAG, ' +
+ 'date=Fri, 09 Nov 2001 01:08:47 -0000>')
+
+ def test___str__(self):
+ ir = self._makeOne()
+ self.assertEqual(ir.__str__(), '')
+
+ def test___str__etag(self):
+ ir = self._makeOne(etag='ETAG', date='Fri, 09 Nov 2001 01:08:47 -0000')
+ self.assertEqual(ir.__str__(), 'ETAG')
+
+ def test___str__date(self):
+ ir = self._makeOne(date='Fri, 09 Nov 2001 01:08:47 -0000')
+ self.assertEqual(ir.__str__(), 'Fri, 09 Nov 2001 01:08:47 -0000')
+
+ def test_match(self):
+ ir = self._makeOne()
+ self.assertTrue(ir.match())
+
+ def test_match_date_none(self):
+ ir = self._makeOne(date='Fri, 09 Nov 2001 01:08:47 -0000')
+ self.assertFalse(ir.match())
+
+ def test_match_date_earlier(self):
+ ir = self._makeOne(date='Fri, 09 Nov 2001 01:08:47 -0000')
+ self.assertTrue(ir.match(last_modified=
+ 'Fri, 09 Nov 2001 01:00:00 -0000'))
+
+ def test_match_etag_none(self):
+ ir = self._makeOne(etag="ETAG")
+ self.assertFalse(ir.match())
+
+ def test_match_etag_different(self):
+ ir = self._makeOne(etag="ETAG")
+ self.assertFalse(ir.match("DIFFERENT"))
+
+ def test_match_response_no_date(self):
+ class DummyResponse(object):
+ etag = "ETAG"
+ last_modified = None
+ ir = self._makeOne(etag="ETAG", date='Fri, 09 Nov 2001 01:08:47 -0000')
+ response = DummyResponse()
+ self.assertFalse(ir.match_response(response))
+
+ def test_match_response_w_date_earlier(self):
+ class DummyResponse(object):
+ etag = "ETAG"
+ last_modified = 'Fri, 09 Nov 2001 01:00:00 -0000'
+ ir = self._makeOne(etag="ETAG", date='Fri, 09 Nov 2001 01:08:47 -0000')
+ response = DummyResponse()
+ self.assertTrue(ir.match_response(response))
+
+ def test_match_response_etag(self):
+ class DummyResponse(object):
+ etag = "ETAG"
+ last_modified = None
+ ir = self._makeOne(etag="ETAG")
+ response = DummyResponse()
+ self.assertTrue(ir.match_response(response))
+
+ def test_parse_none(self):
+ ir = self._makeOne(etag="ETAG")
+ # I believe this identifies a bug: '_NoETag' object is not callable
+ self.assertRaises(TypeError, ir.parse, None)
+
+ def test_parse_wo_gmt(self):
+ ir = self._makeOne(etag="ETAG")
+ res = ir.parse('INTERPRETED_AS_ETAG')
+ self.assertEquals(res.etag.etags, ['INTERPRETED_AS_ETAG'])
+ self.assertEquals(res.date, None)
+
+ def test_parse_with_gmt(self):
+ import datetime
+ class UTC(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return datetime.timedelta(0)
+ def tzname(self, dt):
+ return 'UTC'
+ ir = self._makeOne(etag="ETAG")
+ res = ir.parse('Fri, 09 Nov 2001 01:08:47 GMT')
+ self.assertEquals(res.etag, None)
+ dt = datetime.datetime(2001,11,9, 1,8,47,0, UTC())
+ self.assertEquals(res.date, dt)
+
+class NoIfRangeTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from webob.etag import _NoIfRange
+ return _NoIfRange
+
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
+
+ def test___repr__(self):
+ ir = self._makeOne()
+ self.assertEquals(ir.__repr__(), '<Empty If-Range>')
+
+ def test___str__(self):
+ ir = self._makeOne()
+ self.assertEquals(ir.__str__(), '')
+
+ def test___nonzero__(self):
+ ir = self._makeOne()
+ self.assertEquals(ir.__nonzero__(), False)
+
+ def test_match(self):
+ ir = self._makeOne()
+ self.assertEquals(ir.match(), True)
+
+ def test_match_response(self):
+ ir = self._makeOne()
+ self.assertEquals(ir.match_response("IGNORED"), True)
diff --git a/tests/test_exc.py b/tests/test_exc.py
index 560cfae..7233168 100644
--- a/tests/test_exc.py
+++ b/tests/test_exc.py
@@ -1,7 +1,62 @@
-import sys
-from webob import *
+from webob.request import Request
from webob.dec import wsgify
-from webob.exc import *
+from webob.exc import sys
+from webob.exc import no_escape
+from webob.exc import strip_tags
+from webob.exc import HTTPException
+from webob.exc import WSGIHTTPException
+from webob.exc import HTTPError
+from webob.exc import HTTPRedirection
+from webob.exc import HTTPRedirection
+from webob.exc import HTTPOk
+from webob.exc import HTTPCreated
+from webob.exc import HTTPAccepted
+from webob.exc import HTTPNonAuthoritativeInformation
+from webob.exc import HTTPNoContent
+from webob.exc import HTTPResetContent
+from webob.exc import HTTPPartialContent
+from webob.exc import _HTTPMove
+from webob.exc import HTTPMultipleChoices
+from webob.exc import HTTPMovedPermanently
+from webob.exc import HTTPFound
+from webob.exc import HTTPSeeOther
+from webob.exc import HTTPNotModified
+from webob.exc import HTTPUseProxy
+from webob.exc import HTTPTemporaryRedirect
+from webob.exc import HTTPClientError
+from webob.exc import HTTPBadRequest
+from webob.exc import HTTPUnauthorized
+from webob.exc import HTTPPaymentRequired
+from webob.exc import HTTPForbidden
+from webob.exc import HTTPNotFound
+from webob.exc import HTTPMethodNotAllowed
+from webob.exc import HTTPNotAcceptable
+from webob.exc import HTTPProxyAuthenticationRequired
+from webob.exc import HTTPRequestTimeout
+from webob.exc import HTTPConflict
+from webob.exc import HTTPGone
+from webob.exc import HTTPLengthRequired
+from webob.exc import HTTPPreconditionFailed
+from webob.exc import HTTPRequestEntityTooLarge
+from webob.exc import HTTPRequestURITooLong
+from webob.exc import HTTPUnsupportedMediaType
+from webob.exc import HTTPRequestRangeNotSatisfiable
+from webob.exc import HTTPExpectationFailed
+from webob.exc import HTTPUnprocessableEntity
+from webob.exc import HTTPLocked
+from webob.exc import HTTPFailedDependency
+from webob.exc import HTTPServerError
+from webob.exc import HTTPInternalServerError
+from webob.exc import HTTPNotImplemented
+from webob.exc import HTTPBadGateway
+from webob.exc import HTTPServiceUnavailable
+from webob.exc import HTTPGatewayTimeout
+from webob.exc import HTTPVersionNotSupported
+from webob.exc import HTTPInsufficientStorage
+from webob.exc import HTTPExceptionMiddleware
+from webob import exc
+
+from nose.tools import eq_, ok_, assert_equal, assert_raises
@wsgify
def method_not_allowed_app(req):
@@ -12,6 +67,52 @@ def method_not_allowed_app(req):
raise HTTPMethodNotAllowed().exception
return 'hello!'
+def test_noescape_null():
+ assert_equal(no_escape(None), '')
+
+def test_noescape_not_basestring():
+ assert_equal(no_escape(42), '42')
+
+def test_noescape_unicode():
+ class DummyUnicodeObject(object):
+ def __unicode__(self):
+ return u'42'
+ duo = DummyUnicodeObject()
+ assert_equal(no_escape(duo), u'42')
+
+def test_strip_tags_empty():
+ assert_equal(strip_tags(''), '')
+
+def test_strip_tags_newline_to_space():
+ assert_equal(strip_tags('a\nb'), 'a b')
+
+def test_strip_tags_zaps_carriage_return():
+ assert_equal(strip_tags('a\rb'), 'ab')
+
+def test_strip_tags_br_to_newline():
+ assert_equal(strip_tags('a<br/>b'), 'a\nb')
+
+def test_strip_tags_zaps_comments():
+ assert_equal(strip_tags('a<!--b-->'), 'ab')
+
+def test_strip_tags_zaps_tags():
+ assert_equal(strip_tags('foo<bar>baz</bar>'), 'foobaz')
+
+def test_HTTPException():
+ _called = []
+ _result = object()
+ def _response(environ, start_response):
+ _called.append((environ, start_response))
+ return _result
+ environ = {}
+ start_response = object()
+ exc = HTTPException('testing', _response)
+ ok_(exc.wsgi_response is _response)
+ ok_(exc.exception is exc)
+ result = exc(environ, start_response)
+ ok_(result is result)
+ assert_equal(_called, [(environ, start_response)])
+
def test_exception_with_unicode_data():
req = Request.blank('/', method=u'POST')
res = req.get_response(method_not_allowed_app)
@@ -22,3 +123,238 @@ def test_WSGIHTTPException_headers():
('Set-Cookie', 'a=2')])
mixed = exc.headers.mixed()
assert mixed['set-cookie'] == ['a=1', 'a=2']
+
+def test_WSGIHTTPException_w_body_template():
+ from string import Template
+ TEMPLATE = '$foo: $bar'
+ exc = WSGIHTTPException(body_template = TEMPLATE)
+ assert_equal(exc.body_template, TEMPLATE)
+ ok_(isinstance(exc.body_template_obj, Template))
+ eq_(exc.body_template_obj.substitute({'foo': 'FOO', 'bar': 'BAR'}),
+ 'FOO: BAR')
+
+def test_WSGIHTTPException_w_empty_body():
+ class EmptyOnly(WSGIHTTPException):
+ empty_body = True
+ exc = EmptyOnly(content_type='text/plain', content_length=234)
+ ok_('content_type' not in exc.__dict__)
+ ok_('content_length' not in exc.__dict__)
+
+def test_WSGIHTTPException___str__():
+ exc1 = WSGIHTTPException(detail='Detail')
+ eq_(str(exc1), 'Detail')
+ class Explain(WSGIHTTPException):
+ explanation = 'Explanation'
+ eq_(str(Explain()), 'Explanation')
+
+def test_WSGIHTTPException_plain_body_no_comment():
+ class Explain(WSGIHTTPException):
+ code = '999'
+ title = 'Testing'
+ explanation = 'Explanation'
+ exc = Explain(detail='Detail')
+ eq_(exc.plain_body({}),
+ '999 Testing\n\nExplanation\n\n Detail ')
+
+def test_WSGIHTTPException_html_body_w_comment():
+ class Explain(WSGIHTTPException):
+ code = '999'
+ title = 'Testing'
+ explanation = 'Explanation'
+ exc = Explain(detail='Detail', comment='Comment')
+ eq_(exc.html_body({}),
+ '<html>\n'
+ ' <head>\n'
+ ' <title>999 Testing</title>\n'
+ ' </head>\n'
+ ' <body>\n'
+ ' <h1>999 Testing</h1>\n'
+ ' Explanation<br /><br />\n'
+ 'Detail\n'
+ '<!-- Comment -->\n\n'
+ ' </body>\n'
+ '</html>'
+ )
+
+def test_WSGIHTTPException_generate_response():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'PUT',
+ 'HTTP_ACCEPT': 'text/html'
+ }
+ excep = WSGIHTTPException()
+ assert_equal( excep(environ,start_response), [
+ '<html>\n'
+ ' <head>\n'
+ ' <title>None None</title>\n'
+ ' </head>\n'
+ ' <body>\n'
+ ' <h1>None None</h1>\n'
+ ' <br /><br />\n'
+ '\n'
+ '\n\n'
+ ' </body>\n'
+ '</html>' ]
+ )
+
+def test_WSGIHTTPException_call_w_body():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'PUT'
+ }
+ excep = WSGIHTTPException()
+ excep.body = 'test'
+ assert_equal( excep(environ,start_response), ['test'] )
+
+
+def test_WSGIHTTPException_wsgi_response():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ excep = WSGIHTTPException()
+ assert_equal( excep.wsgi_response(environ,start_response), [] )
+
+def test_WSGIHTTPException_exception_newstyle():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ excep = WSGIHTTPException()
+ exc.newstyle_exceptions = True
+ assert_equal( excep.exception(environ,start_response), [] )
+
+def test_WSGIHTTPException_exception_no_newstyle():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ excep = WSGIHTTPException()
+ exc.newstyle_exceptions = False
+ assert_equal( excep.exception(environ,start_response), [] )
+
+def test_HTTPMove():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ m = _HTTPMove()
+ assert_equal( m( environ, start_response ), [] )
+
+def test_HTTPMove_location_not_none():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ m = _HTTPMove(location='http://example.com')
+ assert_equal( m( environ, start_response ), [] )
+
+def test_HTTPMove_add_slash_and_location():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ assert_raises( TypeError, _HTTPMove, location='http://example.com', add_slash=True )
+
+def test_HTTPMove_call_add_slash():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ m = _HTTPMove()
+ m.add_slash = True
+ assert_equal( m( environ, start_response ), [] )
+
+def test_HTTPMove_call_query_string():
+ def start_response(status, headers, exc_info=None):
+ pass
+ environ = {
+ 'wsgi.url_scheme': 'HTTP',
+ 'SERVER_NAME': 'localhost',
+ 'SERVER_PORT': '80',
+ 'REQUEST_METHOD': 'HEAD'
+ }
+ m = _HTTPMove()
+ m.add_slash = True
+ environ[ 'QUERY_STRING' ] = 'querystring'
+ assert_equal( m( environ, start_response ), [] )
+
+def test_HTTPExceptionMiddleware_ok():
+ def app( environ, start_response ):
+ return '123'
+ application = app
+ m = HTTPExceptionMiddleware(application)
+ environ = {}
+ start_response = None
+ res = m( environ, start_response )
+ assert_equal( res, '123' )
+
+def test_HTTPExceptionMiddleware_exception():
+ def wsgi_response( environ, start_response):
+ return '123'
+ def app( environ, start_response ):
+ raise HTTPException( None, wsgi_response )
+ application = app
+ m = HTTPExceptionMiddleware(application)
+ environ = {}
+ start_response = None
+ res = m( environ, start_response )
+ assert_equal( res, '123' )
+
+def test_HTTPExceptionMiddleware_exception_exc_info_none():
+ class DummySys:
+ def exc_info(self):
+ return None
+ def wsgi_response( environ, start_response):
+ return start_response('200 OK', [], exc_info=None)
+ def app( environ, start_response ):
+ raise HTTPException( None, wsgi_response )
+ application = app
+ m = HTTPExceptionMiddleware(application)
+ environ = {}
+ def start_response(status, headers, exc_info):
+ pass
+ try:
+ from webob import exc
+ old_sys = exc.sys
+ sys = DummySys()
+ res = m( environ, start_response )
+ assert_equal( res, None )
+ finally:
+ exc.sys = old_sys
diff --git a/tests/test_headers.py b/tests/test_headers.py
index e7c40dd..8bcf2d6 100644
--- a/tests/test_headers.py
+++ b/tests/test_headers.py
@@ -1,21 +1,24 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from webob import headers
-from nose.tools import ok_, assert_raises, eq_ as eq
+from nose.tools import ok_, assert_raises, eq_
class TestError(Exception):
pass
-def test_raise_keyerror():
- """Deleting a missing key from ResponseHeaders should raise a KeyError
- Deleting a present key should not raise an error at all
- """
+def test_ResponseHeaders_delitem_notpresent():
+ """Deleting a missing key from ResponseHeaders should raise a KeyError"""
d = headers.ResponseHeaders()
assert_raises(KeyError, d.__delitem__, 'b')
+
+def test_ResponseHeaders_delitem_present():
+ """
+ Deleting a present key should not raise an error at all
+ """
d = headers.ResponseHeaders(a=1)
del d['a']
ok_('a' not in d)
-def test_set_default():
+def test_ResponseHeaders_setdefault():
"""Testing set_default for ResponseHeaders"""
d = headers.ResponseHeaders(a=1)
res = d.setdefault('b', 1)
@@ -25,23 +28,86 @@ def test_set_default():
res = d.setdefault('B', 10)
assert res == d['b'] == d['B'] == 1
-def test_pop():
+def test_ResponseHeader_pop():
"""Testing if pop return TypeError when more than len(*args)>1 plus other
assorted tests"""
d = headers.ResponseHeaders(a=1, b=2, c=3, d=4)
assert_raises(TypeError, d.pop, 'a', 'z', 'y')
- assert d.pop('a') == 1
- assert 'a' not in d
- assert d.pop('B') == 2
- assert 'b' not in d
- assert d.pop('c', 'u') == 3
- assert 'c' not in d
- assert d.pop('e', 'u') =='u'
- assert 'e' not in d
+ eq_(d.pop('a'), 1)
+ ok_('a' not in d)
+ eq_(d.pop('B'), 2)
+ ok_('b' not in d)
+ eq_(d.pop('c', 'u'), 3)
+ ok_('c' not in d)
+ eq_(d.pop('e', 'u'), 'u')
+ ok_('e' not in d)
assert_raises(KeyError, d.pop, 'z')
-def test_delitem_environheaders():
+def test_ResponseHeaders_getitem_miss():
+ d = headers.ResponseHeaders()
+ assert_raises(KeyError, d.__getitem__, 'a')
+
+def test_ResponseHeaders_getall():
+ d = headers.ResponseHeaders()
+ d.add('a', 1)
+ d.add('a', 2)
+ result = d.getall('a')
+ eq_(result, [1,2])
+
+def test_ResponseHeaders_mixed():
+ d = headers.ResponseHeaders()
+ d.add('a', 1)
+ d.add('a', 2)
+ d['b'] = 1
+ result = d.mixed()
+ eq_(result, {'a':[1,2], 'b':1})
+
+def test_ResponseHeaders_setitem_scalar_replaces_seq():
+ d = headers.ResponseHeaders()
+ d.add('a', 2)
+ d['a'] = 1
+ result = d.getall('a')
+ eq_(result, [1])
+
+def test_ResponseHeaders_contains():
+ d = headers.ResponseHeaders()
+ d['a'] = 1
+ ok_('a' in d)
+ ok_(not 'b' in d)
+
+def test_EnvironHeaders_delitem():
d = headers.EnvironHeaders({'CONTENT_LENGTH': '10'})
del d['CONTENT-LENGTH']
assert not d
assert_raises(KeyError, d.__delitem__, 'CONTENT-LENGTH')
+
+def test_EnvironHeaders_getitem():
+ d = headers.EnvironHeaders({'CONTENT_LENGTH': '10'})
+ eq_(d['CONTENT-LENGTH'], '10')
+
+def test_EnvironHeaders_setitem():
+ d = headers.EnvironHeaders({})
+ d['abc'] = '10'
+ eq_(d['abc'], '10')
+
+def test_EnvironHeaders_contains():
+ d = headers.EnvironHeaders({})
+ d['a'] = '10'
+ ok_('a' in d)
+ ok_(not 'b' in d)
+
+def test__trans_key_not_basestring():
+ result = headers._trans_key(None)
+ eq_(result, None)
+
+def test__trans_key_not_a_header():
+ result = headers._trans_key('')
+ eq_(result, None)
+
+def test__trans_key_key2header():
+ result = headers._trans_key('CONTENT_TYPE')
+ eq_(result, 'Content-Type')
+
+def test__trans_key_httpheader():
+ result = headers._trans_key('HTTP_FOO_BAR')
+ eq_(result, 'Foo-Bar')
diff --git a/tests/test_multidict.py b/tests/test_multidict.py
index b4db015..d3ec57e 100644
--- a/tests/test_multidict.py
+++ b/tests/test_multidict.py
@@ -3,98 +3,213 @@
import unittest
from webob import multidict
-class MultiDictTestCase(unittest.TestCase):
- klass = multidict.MultiDict
- data = multidict.MultiDict([('a', u'\xe9'), ('a', 'e'), ('b', 1)])
-
+class BaseDictTests(object):
def setUp(self):
- self.d = self._get_instance()
+ self._list = [('a', u'\xe9'), ('a', 'e'), ('a', 'f'), ('b', 1)]
+ self.data = multidict.MultiDict(self._list)
+ self.d = self._get_instance()
def _get_instance(self):
return self.klass(self.data.copy())
def test_len(self):
- assert len(self.d) == 3
+ self.assertEqual(len(self.d), 4)
def test_getone(self):
- assert self.d.getone('b') == 1
+ self.assertEqual(self.d.getone('b'), 1)
+
+ def test_getone_missing(self):
+ self.assertRaises(KeyError, self.d.getone, 'z')
+
+ def test_getone_multiple_raises(self):
self.assertRaises(KeyError, self.d.getone, 'a')
def test_getall(self):
- assert self.d.getall('b') == [1]
+ self.assertEqual(self.d.getall('b'), [1])
def test_dict_of_lists(self):
- assert self.d.dict_of_lists() == {'a': [u'\xe9', u'e'], 'b': [1]}, self.d.dict_of_lists()
+ self.assertEqual(
+ self.d.dict_of_lists(),
+ {'a': [u'\xe9', u'e', u'f'], 'b': [1]})
def test_dict_api(self):
- assert 'a' in self.d.mixed()
- assert 'a' in self.d.keys()
- assert 'a' in self.d.iterkeys()
- assert ('b', 1) in self.d.items()
- assert ('b', 1) in self.d.iteritems()
- assert 1 in self.d.values()
- assert 1 in self.d.itervalues()
- assert len(self.d) == 3
+ self.assertTrue('a' in self.d.mixed())
+ self.assertTrue('a' in self.d.keys())
+ self.assertTrue('a' in self.d.iterkeys())
+ self.assertTrue(('b', 1) in self.d.items())
+ self.assertTrue(('b', 1) in self.d.iteritems())
+ self.assertTrue(1 in self.d.values())
+ self.assertTrue(1 in self.d.itervalues())
+ self.assertEqual(len(self.d), 4)
def test_set_del_item(self):
d = self._get_instance()
- assert 'a' in d
+ self.assertTrue('a' in d)
del d['a']
- assert 'a' not in d
- d['a'] = 1
+ self.assertTrue(not 'a' in d)
def test_pop(self):
d = self._get_instance()
d['a'] = 1
- assert d.pop('a') == 1
- assert d.pop('x', 1) == 1
- assert d.popitem() == ('b', 1)
+ self.assertEqual(d.pop('a'), 1)
+ self.assertEqual(d.pop('x', 1), 1)
+
+ def test_pop_wrong_args(self):
+ d = self._get_instance()
+ self.assertRaises(TypeError, d.pop, 'a', 1, 1)
+
+ def test_pop_missing(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.pop, 'z')
+
+ def test_popitem(self):
+ d = self._get_instance()
+ self.assertEqual(d.popitem(), ('b', 1))
def test_update(self):
d = self._get_instance()
d.update(e=1)
- assert 'e' in d
+ self.assertTrue('e' in d)
d.update(dict(x=1))
- assert 'x' in d
+ self.assertTrue('x' in d)
d.update([('y', 1)])
- assert 'y' in d
+ self.assertTrue('y' in d)
def test_setdefault(self):
d = self._get_instance()
d.setdefault('a', 1)
- assert d['a'] != 1
+ self.assertNotEqual(d['a'], 1)
d.setdefault('e', 1)
- assert 'e' in d
+ self.assertTrue('e' in d)
def test_add(self):
d = self._get_instance()
d.add('b', 3)
- assert d.getall('b') == [1, 3]
+ self.assertEqual(d.getall('b'), [1, 3])
def test_copy(self):
assert self.d.copy() is not self.d
if hasattr(self.d, 'multi'):
- assert self.d.copy().multi is not self.d.multi
- assert self.d.copy() is not self.d.multi
+ self.assertFalse(self.d.copy().multi is self.d.multi)
+ self.assertFalse(self.d.copy() is self.d.multi)
def test_clear(self):
d = self._get_instance()
d.clear()
- assert len(d) == 0
+ self.assertEqual(len(d), 0)
def test_nonzero(self):
d = self._get_instance()
- assert d
+ self.assertTrue(d)
d.clear()
- assert not d
+ self.assertFalse(d)
+
+ def test_repr(self):
+ self.assertTrue(repr(self._get_instance()))
+ def test_too_many_args(self):
+ from webob.multidict import MultiDict
+ self.assertRaises(TypeError, MultiDict, 1, 2)
-class UnicodeMultiDictTestCase(MultiDictTestCase):
+ def test_no_args(self):
+ from webob.multidict import MultiDict
+ md = MultiDict()
+ self.assertEqual(md._items, [])
+
+ def test_kwargs(self):
+ from webob.multidict import MultiDict
+ md = MultiDict(kw1='val1')
+ self.assertEqual(md._items, [('kw1','val1')])
+
+ def test_view_list_not_list(self):
+ from webob.multidict import MultiDict
+ d = MultiDict()
+ self.assertRaises(TypeError, d.view_list, 42)
+
+ def test_view_list(self):
+ from webob.multidict import MultiDict
+ d = MultiDict()
+ self.assertEqual(d.view_list([1,2])._items, [1,2])
+
+ def test_from_fieldstorage_with_filename(self):
+ from webob.multidict import MultiDict
+ d = MultiDict()
+ fs = DummyFieldStorage('a', '1', 'file')
+ self.assertEqual(d.from_fieldstorage(fs), MultiDict({'a':fs.list[0]}))
+
+ def test_from_fieldstorage_without_filename(self):
+ from webob.multidict import MultiDict
+ d = MultiDict()
+ fs = DummyFieldStorage('a', '1')
+ self.assertEqual(d.from_fieldstorage(fs), MultiDict({'a':'1'}))
+
+class MultiDictTestCase(BaseDictTests, unittest.TestCase):
+ klass = multidict.MultiDict
+
+ def test_update_behavior_warning(self):
+ import warnings
+ class Foo(dict):
+ def __len__(self):
+ return 0
+ foo = Foo()
+ foo['a'] = 1
+ d = self._get_instance()
+ try:
+ warnings.simplefilter('error')
+ self.assertRaises(UserWarning, d.update, foo)
+ finally:
+ warnings.resetwarnings()
+
+class UnicodeMultiDictTestCase(BaseDictTests, unittest.TestCase):
klass = multidict.UnicodeMultiDict
-class NestedMultiDictTestCase(MultiDictTestCase):
+ def test_decode_key(self):
+ d = self._get_instance()
+ d.decode_keys = True
+
+ class Key(object):
+ pass
+
+ key = Key()
+ self.assertEquals(key, d._decode_key(key))
+
+ def test_decode_value(self):
+ import cgi
+
+ d = self._get_instance()
+ d.decode_keys = True
+
+ fs = cgi.FieldStorage()
+ fs.name = 'a'
+ self.assertEqual(d._decode_value(fs).name, 'a')
+
+ def test_encode_key(self):
+ d = self._get_instance()
+ value = unicode('a')
+ d.decode_keys = True
+ self.assertEquals(d._encode_key(value),'a')
+
+ def test_encode_value(self):
+ d = self._get_instance()
+ value = unicode('a')
+ self.assertEquals(d._encode_value(value),'a')
+
+class NestedMultiDictTestCase(BaseDictTests, unittest.TestCase):
klass = multidict.NestedMultiDict
+ def test_getitem(self):
+ d = self.klass({'a':1})
+ self.assertEqual(d['a'], 1)
+
+ def test_getitem_raises(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.__getitem__, 'z')
+
+ def test_contains(self):
+ d = self._get_instance()
+ self.assertEquals(d.__contains__('a'), True)
+ self.assertEquals(d.__contains__('z'), False)
+
def test_add(self):
d = self._get_instance()
self.assertRaises(KeyError, d.add, 'b', 3)
@@ -119,17 +234,156 @@ class NestedMultiDictTestCase(MultiDictTestCase):
self.assertRaises(KeyError, d.pop, 'a')
self.assertRaises(KeyError, d.pop, 'a', 1)
+ def test_popitem(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.popitem, 'a')
+
+ def test_pop_wrong_args(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.pop, 'a', 1, 1)
+
def test_clear(self):
d = self._get_instance()
self.assertRaises(KeyError, d.clear)
def test_nonzero(self):
d = self._get_instance()
- assert d
+ self.assertEqual(d.__nonzero__(), True)
+ d.dicts = [{}]
+ self.assertEqual(d.__nonzero__(), False)
+ assert not d
-class TrackableMultiDict(MultiDictTestCase):
+class TrackableMultiDict(BaseDictTests, unittest.TestCase):
klass = multidict.TrackableMultiDict
def _get_instance(self):
def tracker(*args, **kwargs): pass
return self.klass(self.data.copy(), __tracker=tracker, __name='tracker')
+
+ def test_inititems(self):
+ #The first argument passed into the __init__ method
+ class Arg:
+ def items(self):
+ return [('a', u'\xe9'), ('a', 'e'), ('a', 'f'), ('b', 1)]
+
+ d = self._get_instance()
+ d._items = None
+ d.__init__(Arg())
+ self.assertEquals(self.d._items, self._list)
+
+ def test_nullextend(self):
+ d = self._get_instance()
+ self.assertEqual(d.extend(), None)
+ d.extend(test = 'a')
+ self.assertEqual(d['test'], 'a')
+
+ def test_listextend(self):
+ class Other:
+ def items(self):
+ return [u'\xe9', u'e', r'f', 1]
+
+ other = Other()
+ d = self._get_instance()
+ d.extend(other)
+
+ _list = [u'\xe9', u'e', r'f', 1]
+ for v in _list:
+ self.assertTrue(v in d._items)
+
+ def test_dictextend(self):
+ class Other:
+ def __getitem__(self, item):
+ return {'a':1, 'b':2, 'c':3}.get(item)
+
+ def keys(self):
+ return ['a', 'b', 'c']
+
+ other = Other()
+ d = self._get_instance()
+ d.extend(other)
+
+ _list = [('a', 1), ('b', 2), ('c', 3)]
+ for v in _list:
+ self.assertTrue(v in d._items)
+
+ def test_otherextend(self):
+ class Other(object):
+ def __iter__(self):
+ return iter([('a', 1)])
+
+ other = Other()
+ d = self._get_instance()
+ d.extend(other)
+
+ _list = [('a', 1)]
+ for v in _list:
+ self.assertTrue(v in d._items)
+
+class NoVarsTestCase(unittest.TestCase):
+ klass = multidict.NoVars
+
+ def _get_instance(self):
+ return self.klass()
+
+ def test_getitem(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.__getitem__, 'a')
+
+ def test_setitem(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.__setitem__, 'a')
+
+ def test_delitem(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.__delitem__, 'a')
+
+ def test_get(self):
+ d = self._get_instance()
+ self.assertEqual(d.get('a', default = 'b'), 'b')
+
+ def test_getall(self):
+ d = self._get_instance()
+ self.assertEqual(d.getall('a'), [])
+
+ def test_getone(self):
+ d = self._get_instance()
+ self.assertRaises(KeyError, d.getone, 'a')
+
+ def test_mixed(self):
+ d = self._get_instance()
+ self.assertEqual(d.mixed(), {})
+
+ def test_contains(self):
+ d = self._get_instance()
+ self.assertEqual(d.__contains__('a'), False)
+
+ def test_copy(self):
+ d = self._get_instance()
+ self.assertEqual(d.copy(), d)
+
+ def test_len(self):
+ d = self._get_instance()
+ self.assertEqual(len(d), 0)
+
+ def test_repr(self):
+ d = self._get_instance()
+ self.assertEqual(repr(d), '<NoVars: N/A>')
+
+ def test_keys(self):
+ d = self._get_instance()
+ self.assertEqual(d.keys(), [])
+
+ def test_iterkeys(self):
+ d = self._get_instance()
+ self.assertEqual(list(d.iterkeys()), [])
+
+class DummyField(object):
+ def __init__(self, name, value, filename=None):
+ self.name = name
+ self.value = value
+ self.filename = filename
+
+class DummyFieldStorage(object):
+ def __init__(self, name, value, filename=None):
+ self.list = [DummyField(name, value, filename)]
+
diff --git a/tests/test_request.py b/tests/test_request.py
index ae7bad8..301480e 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -1,14 +1,2271 @@
-from webob import Request, BaseRequest
-from webob.request import (
- NoDefault, AdhocAttrMixin, environ_from_url, environ_add_POST
-)
-from webtest import TestApp
-from nose.tools import eq_, ok_, assert_raises, assert_false
-from cStringIO import StringIO
-import string
-import cgi
+import unittest
+
+_marker = object()
+
+class BaseRequestTests(unittest.TestCase):
+
+ def _getTargetClass(self):
+ from webob import BaseRequest
+ return BaseRequest
+
+ def _makeOne(self, environ, environ_getter=None, charset=_marker,
+ unicode_errors=_marker, decode_param_names=_marker, **kw):
+ from webob.request import NoDefault
+ if charset is _marker:
+ charset = NoDefault
+ if unicode_errors is _marker:
+ unicode_errors = NoDefault
+ if decode_param_names is _marker:
+ decode_param_names = NoDefault
+ return self._getTargetClass()(environ, environ_getter, charset,
+ unicode_errors, decode_param_names, **kw)
+
+ def _makeStringIO(self, text):
+ try:
+ from io import BytesIO
+ except ImportError: # Python < 2.6
+ from StringIO import StringIO as BytesIO
+ return BytesIO(text)
+
+ def test_ctor_environ_getter_raises_WTF(self):
+ # This API should be changed.
+ self.assertRaises(ValueError,
+ self._makeOne, {}, environ_getter=object())
+
+ def test_ctor_wo_environ_raises_WTF(self):
+ # This API should be changed.
+ self.assertRaises(TypeError, self._makeOne, None)
+
+ def test_ctor_w_environ(self):
+ environ = {}
+ req = self._makeOne(environ)
+ self.assertEqual(req.environ, environ)
+
+ def test_body_file_getter(self):
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT}
+ req = self._makeOne(environ)
+ self.assert_(req.body_file is INPUT)
+
+ def test_body_file_setter_w_string(self):
+ BEFORE = self._makeStringIO('before')
+ AFTER = str('AFTER')
+ environ = {'wsgi.input': BEFORE,
+ 'CONTENT_LENGTH': len('before'),
+ }
+ req = self._makeOne(environ)
+ req.body_file = AFTER
+ self.assertEqual(req.body_file.getvalue(), AFTER)
+ self.assertEqual(req.content_length, len(AFTER))
+
+ def test_body_file_setter_non_string(self):
+ BEFORE = self._makeStringIO('before')
+ AFTER = self._makeStringIO('after')
+ environ = {'wsgi.input': BEFORE,
+ 'CONTENT_LENGTH': len('before'),
+ }
+ req = self._makeOne(environ)
+ req.body_file = AFTER
+ self.assert_(req.body_file is AFTER)
+ self.assertEqual(req.content_length, None)
+
+ def test_body_file_deleter(self):
+ INPUT = self._makeStringIO('before')
+ environ = {'wsgi.input': INPUT,
+ 'CONTENT_LENGTH': len('before'),
+ }
+ req = self._makeOne(environ)
+ del req.body_file
+ self.assertEqual(req.body_file.getvalue(), '')
+ self.assertEqual(req.content_length, 0)
+
+ def test_body_file_raw(self):
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'CONTENT_LENGTH': len('input'),
+ }
+ req = self._makeOne(environ)
+ self.assert_(req.body_file_raw is INPUT)
+
+ def test_body_file_seekable_input_not_seekable(self):
+ INPUT = self._makeStringIO('input')
+ INPUT.seek(1, 0) # consume
+ environ = {'wsgi.input': INPUT,
+ 'webob.is_body_seekable': False,
+ 'CONTENT_LENGTH': len('input'),
+ }
+ req = self._makeOne(environ)
+ seekable = req.body_file_seekable
+ self.assert_(seekable is not INPUT)
+ self.assertEqual(seekable.getvalue(), 'nput')
+
+ def test_body_file_seekable_input_is_seekable(self):
+ INPUT = self._makeStringIO('input')
+ INPUT.seek(1, 0) # consume
+ environ = {'wsgi.input': INPUT,
+ 'webob.is_body_seekable': True,
+ 'CONTENT_LENGTH': len('input'),
+ }
+ req = self._makeOne(environ)
+ seekable = req.body_file_seekable
+ self.assert_(seekable is INPUT)
+
+ def test_scheme(self):
+ environ = {'wsgi.url_scheme': 'something:',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.scheme, 'something:')
+
+ def test_method(self):
+ environ = {'REQUEST_METHOD': 'OPTIONS',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.method, 'OPTIONS')
+
+ def test_http_version(self):
+ environ = {'SERVER_PROTOCOL': '1.1',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.http_version, '1.1')
+
+ def test_script_name(self):
+ environ = {'SCRIPT_NAME': '/script',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.script_name, '/script')
+
+ def test_path_info(self):
+ environ = {'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.path_info, '/path/info')
+
+ def test_content_length_getter(self):
+ environ = {'CONTENT_LENGTH': '1234',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.content_length, 1234)
+
+ def test_content_length_setter_w_str(self):
+ environ = {'CONTENT_LENGTH': '1234',
+ }
+ req = self._makeOne(environ)
+ req.content_length = '3456'
+ self.assertEqual(req.content_length, 3456)
+
+ def test_remote_user(self):
+ environ = {'REMOTE_USER': 'phred',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.remote_user, 'phred')
+
+ def test_remote_addr(self):
+ environ = {'REMOTE_ADDR': '1.2.3.4',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.remote_addr, '1.2.3.4')
+
+ def test_query_string(self):
+ environ = {'QUERY_STRING': 'foo=bar&baz=bam',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.query_string, 'foo=bar&baz=bam')
+
+ def test_server_name(self):
+ environ = {'SERVER_NAME': 'somehost.tld',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.server_name, 'somehost.tld')
+
+ def test_server_port_getter(self):
+ environ = {'SERVER_PORT': '6666',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.server_port, 6666)
+
+ def test_server_port_setter_with_string(self):
+ environ = {'SERVER_PORT': '6666',
+ }
+ req = self._makeOne(environ)
+ req.server_port = '6667'
+ self.assertEqual(req.server_port, 6667)
+
+ def test_uscript_name(self):
+ environ = {'SCRIPT_NAME': '/script',
+ }
+ req = self._makeOne(environ)
+ self.assert_(isinstance(req.uscript_name, unicode))
+ self.assertEqual(req.uscript_name, '/script')
+
+ def test_upath_info(self):
+ environ = {'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ self.assert_(isinstance(req.upath_info, unicode))
+ self.assertEqual(req.upath_info, '/path/info')
+
+ def test_content_type_getter_no_parameters(self):
+ environ = {'CONTENT_TYPE': 'application/xml+foobar',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.content_type, 'application/xml+foobar')
+
+ def test_content_type_getter_w_parameters(self):
+ environ = {'CONTENT_TYPE': 'application/xml+foobar;charset="utf8"',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.content_type, 'application/xml+foobar')
+
+ def test_content_type_setter_w_None(self):
+ environ = {'CONTENT_TYPE': 'application/xml+foobar;charset="utf8"',
+ }
+ req = self._makeOne(environ)
+ req.content_type = None
+ self.assertEqual(req.content_type, '')
+ self.assert_('CONTENT_TYPE' not in environ)
+
+ def test_content_type_setter_existing_paramter_no_new_paramter(self):
+ environ = {'CONTENT_TYPE': 'application/xml+foobar;charset="utf8"',
+ }
+ req = self._makeOne(environ)
+ req.content_type = 'text/xml'
+ self.assertEqual(req.content_type, 'text/xml')
+ self.assertEqual(environ['CONTENT_TYPE'], 'text/xml;charset="utf8"')
+
+ def test_content_type_deleter_clears_environ_value(self):
+ environ = {'CONTENT_TYPE': 'application/xml+foobar;charset="utf8"',
+ }
+ req = self._makeOne(environ)
+ del req.content_type
+ self.assertEqual(req.content_type, '')
+ self.assert_('CONTENT_TYPE' not in environ)
+
+ def test_content_type_deleter_no_environ_value(self):
+ environ = {}
+ req = self._makeOne(environ)
+ del req.content_type
+ self.assertEqual(req.content_type, '')
+ self.assert_('CONTENT_TYPE' not in environ)
+
+ def test_charset_getter_cache_hit(self):
+ CT = 'application/xml+foobar'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ req._charset_cache = (CT, 'cp1252')
+ self.assertEqual(req.charset, 'cp1252')
+
+ def test_charset_getter_cache_miss_w_parameter(self):
+ CT = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.charset, 'utf8')
+ self.assertEqual(req._charset_cache, (CT, 'utf8'))
+
+ def test_charset_getter_cache_miss_wo_parameter(self):
+ CT = 'application/xml+foobar'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.charset, 'UTF-8')
+ self.assertEqual(req._charset_cache, (CT, 'UTF-8'))
+
+ def test_charset_setter_None_w_parameter(self):
+ CT = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ req.charset = None
+ self.assertEqual(environ['CONTENT_TYPE'], 'application/xml+foobar')
+ self.assertEqual(req.charset, 'UTF-8')
+
+ def test_charset_setter_empty_w_parameter(self):
+ CT = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ req.charset = ''
+ self.assertEqual(environ['CONTENT_TYPE'], 'application/xml+foobar')
+ self.assertEqual(req.charset, 'UTF-8')
+
+ def test_charset_setter_nonempty_w_parameter(self):
+ CT = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ req.charset = 'cp1252'
+ self.assertEqual(environ['CONTENT_TYPE'],
+ #'application/xml+foobar; charset="cp1252"') WTF?
+ 'application/xml+foobar;charset=cp1252',
+ )
+ self.assertEqual(req.charset, 'cp1252')
+
+ def test_charset_setter_nonempty_wo_parameter(self):
+ CT = 'application/xml+foobar'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ req.charset = 'cp1252'
+ self.assertEqual(environ['CONTENT_TYPE'],
+ 'application/xml+foobar; charset="cp1252"',
+ #'application/xml+foobar;charset=cp1252', WTF?
+ )
+ self.assertEqual(req.charset, 'cp1252')
+
+ def test_charset_deleter_w_parameter(self):
+ CT = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CT,
+ }
+ req = self._makeOne(environ)
+ del req.charset
+ self.assertEqual(environ['CONTENT_TYPE'], 'application/xml+foobar')
+ self.assertEqual(req.charset, 'UTF-8')
+
+ def test_headers_getter_miss(self):
+ CONTENT_TYPE = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CONTENT_TYPE,
+ 'CONTENT_LENGTH': '123',
+ }
+ req = self._makeOne(environ)
+ headers = req.headers
+ self.assertEqual(headers,
+ {'Content-Type': CONTENT_TYPE,
+ 'Content-Length': '123'})
+ self.assertEqual(req._headers, headers)
+
+ def test_headers_getter_hit(self):
+ CONTENT_TYPE = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CONTENT_TYPE,
+ 'CONTENT_LENGTH': '123',
+ }
+ req = self._makeOne(environ)
+ req._headers = {'Foo': 'Bar'}
+ self.assertEqual(req.headers,
+ {'Foo': 'Bar'})
+
+ def test_headers_setter(self):
+ CONTENT_TYPE = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CONTENT_TYPE,
+ 'CONTENT_LENGTH': '123',
+ }
+ req = self._makeOne(environ)
+ req._headers = {'Foo': 'Bar'}
+ req.headers = {'Qux': 'Spam'}
+ self.assertEqual(req.headers,
+ {'Qux': 'Spam'})
+
+ def test_no_headers_deleter(self):
+ CONTENT_TYPE = 'application/xml+foobar;charset="utf8"'
+ environ = {'CONTENT_TYPE': CONTENT_TYPE,
+ 'CONTENT_LENGTH': '123',
+ }
+ req = self._makeOne(environ)
+ def _test():
+ del req.headers
+ self.assertRaises(AttributeError, _test)
+
+ def test_host_url_w_http_host_and_no_port(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'example.com',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'http://example.com')
+
+ def test_host_url_w_http_host_and_standard_port(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'example.com:80',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'http://example.com')
+
+ def test_host_url_w_http_host_and_oddball_port(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'example.com:8888',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'http://example.com:8888')
+
+ def test_host_url_w_http_host_https_and_no_port(self):
+ environ = {'wsgi.url_scheme': 'https',
+ 'HTTP_HOST': 'example.com',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'https://example.com')
+
+ def test_host_url_w_http_host_https_and_standard_port(self):
+ environ = {'wsgi.url_scheme': 'https',
+ 'HTTP_HOST': 'example.com:443',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'https://example.com')
+
+ def test_host_url_w_http_host_https_and_oddball_port(self):
+ environ = {'wsgi.url_scheme': 'https',
+ 'HTTP_HOST': 'example.com:4333',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'https://example.com:4333')
+
+ def test_host_url_wo_http_host(self):
+ environ = {'wsgi.url_scheme': 'https',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '4333',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.host_url, 'https://example.com:4333')
+
+ def test_application_url(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.application_url, 'http://example.com/script')
+
+ def test_path_url(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.path_url, 'http://example.com/script/path/info')
+
+ def test_path(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.path, '/script/path/info')
+
+ def test_path_qs_no_qs(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.path_qs, '/script/path/info')
+
+ def test_path_qs_w_qs(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ 'QUERY_STRING': 'foo=bar&baz=bam'
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.path_qs, '/script/path/info?foo=bar&baz=bam')
+
+ def test_url_no_qs(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.url, 'http://example.com/script/path/info')
+
+ def test_url_w_qs(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ 'QUERY_STRING': 'foo=bar&baz=bam'
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.url,
+ 'http://example.com/script/path/info?foo=bar&baz=bam')
+
+ def test_relative_url_to_app_true_wo_leading_slash(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ 'QUERY_STRING': 'foo=bar&baz=bam'
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.relative_url('other/page', True),
+ 'http://example.com/script/other/page')
+
+ def test_relative_url_to_app_true_w_leading_slash(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ 'QUERY_STRING': 'foo=bar&baz=bam'
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.relative_url('/other/page', True),
+ 'http://example.com/other/page')
+
+ def test_relative_url_to_app_false_other_w_leading_slash(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ 'QUERY_STRING': 'foo=bar&baz=bam'
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.relative_url('/other/page', False),
+ 'http://example.com/other/page')
+
+ def test_relative_url_to_app_false_other_wo_leading_slash(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ 'QUERY_STRING': 'foo=bar&baz=bam'
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.relative_url('other/page', False),
+ 'http://example.com/script/path/other/page')
+
+ def test_path_info_pop_empty(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '',
+ }
+ req = self._makeOne(environ)
+ popped = req.path_info_pop()
+ self.assertEqual(popped, None)
+ self.assertEqual(environ['SCRIPT_NAME'], '/script')
+
+ def test_path_info_pop_just_leading_slash(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/',
+ }
+ req = self._makeOne(environ)
+ popped = req.path_info_pop()
+ self.assertEqual(popped, '')
+ self.assertEqual(environ['SCRIPT_NAME'], '/script/')
+ self.assertEqual(environ['PATH_INFO'], '')
+
+ def test_path_info_pop_non_empty_no_pattern(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ popped = req.path_info_pop()
+ self.assertEqual(popped, 'path')
+ self.assertEqual(environ['SCRIPT_NAME'], '/script/path')
+ self.assertEqual(environ['PATH_INFO'], '/info')
+
+ def test_path_info_pop_non_empty_w_pattern_miss(self):
+ import re
+ PATTERN = re.compile('miss')
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ popped = req.path_info_pop(PATTERN)
+ self.assertEqual(popped, None)
+ self.assertEqual(environ['SCRIPT_NAME'], '/script')
+ self.assertEqual(environ['PATH_INFO'], '/path/info')
+
+ def test_path_info_pop_non_empty_w_pattern_hit(self):
+ import re
+ PATTERN = re.compile('path')
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path/info',
+ }
+ req = self._makeOne(environ)
+ popped = req.path_info_pop(PATTERN)
+ self.assertEqual(popped, 'path')
+ self.assertEqual(environ['SCRIPT_NAME'], '/script/path')
+ self.assertEqual(environ['PATH_INFO'], '/info')
+
+ def test_path_info_pop_skips_empty_elements(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '//path/info',
+ }
+ req = self._makeOne(environ)
+ popped = req.path_info_pop()
+ self.assertEqual(popped, 'path')
+ self.assertEqual(environ['SCRIPT_NAME'], '/script//path')
+ self.assertEqual(environ['PATH_INFO'], '/info')
+
+ def test_path_info_peek_empty(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '',
+ }
+ req = self._makeOne(environ)
+ peeked = req.path_info_peek()
+ self.assertEqual(peeked, None)
+ self.assertEqual(environ['SCRIPT_NAME'], '/script')
+ self.assertEqual(environ['PATH_INFO'], '')
+
+ def test_path_info_peek_just_leading_slash(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/',
+ }
+ req = self._makeOne(environ)
+ peeked = req.path_info_peek()
+ self.assertEqual(peeked, '')
+ self.assertEqual(environ['SCRIPT_NAME'], '/script')
+ self.assertEqual(environ['PATH_INFO'], '/')
+
+ def test_path_info_peek_non_empty(self):
+ environ = {'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/script',
+ 'PATH_INFO': '/path',
+ }
+ req = self._makeOne(environ)
+ peeked = req.path_info_peek()
+ self.assertEqual(peeked, 'path')
+ self.assertEqual(environ['SCRIPT_NAME'], '/script')
+ self.assertEqual(environ['PATH_INFO'], '/path')
+
+ def test_urlvars_getter_w_paste_key(self):
+ environ = {'paste.urlvars': {'foo': 'bar'},
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.urlvars, {'foo': 'bar'})
+
+ def test_urlvars_getter_w_wsgiorg_key(self):
+ environ = {'wsgiorg.routing_args': ((), {'foo': 'bar'}),
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.urlvars, {'foo': 'bar'})
+
+ def test_urlvars_getter_wo_keys(self):
+ environ = {}
+ req = self._makeOne(environ)
+ self.assertEqual(req.urlvars, {})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {}))
+
+ def test_urlvars_setter_w_paste_key(self):
+ environ = {'paste.urlvars': {'foo': 'bar'},
+ }
+ req = self._makeOne(environ)
+ req.urlvars = {'baz': 'bam'}
+ self.assertEqual(req.urlvars, {'baz': 'bam'})
+ self.assertEqual(environ['paste.urlvars'], {'baz': 'bam'})
+ self.assert_('wsgiorg.routing_args' not in environ)
+
+ def test_urlvars_setter_w_wsgiorg_key(self):
+ environ = {'wsgiorg.routing_args': ((), {'foo': 'bar'}),
+ 'paste.urlvars': {'qux': 'spam'},
+ }
+ req = self._makeOne(environ)
+ req.urlvars = {'baz': 'bam'}
+ self.assertEqual(req.urlvars, {'baz': 'bam'})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {'baz': 'bam'}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlvars_setter_wo_keys(self):
+ environ = {}
+ req = self._makeOne(environ)
+ req.urlvars = {'baz': 'bam'}
+ self.assertEqual(req.urlvars, {'baz': 'bam'})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {'baz': 'bam'}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlvars_deleter_w_paste_key(self):
+ environ = {'paste.urlvars': {'foo': 'bar'},
+ }
+ req = self._makeOne(environ)
+ del req.urlvars
+ self.assertEqual(req.urlvars, {})
+ self.assert_('paste.urlvars' not in environ)
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {}))
+
+ def test_urlvars_deleter_w_wsgiorg_key_non_empty_tuple(self):
+ environ = {'wsgiorg.routing_args': (('a', 'b'), {'foo': 'bar'}),
+ 'paste.urlvars': {'qux': 'spam'},
+ }
+ req = self._makeOne(environ)
+ del req.urlvars
+ self.assertEqual(req.urlvars, {})
+ self.assertEqual(environ['wsgiorg.routing_args'], (('a', 'b'), {}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlvars_deleter_w_wsgiorg_key_empty_tuple(self):
+ environ = {'wsgiorg.routing_args': ((), {'foo': 'bar'}),
+ 'paste.urlvars': {'qux': 'spam'},
+ }
+ req = self._makeOne(environ)
+ del req.urlvars
+ self.assertEqual(req.urlvars, {})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlvars_deleter_wo_keys(self):
+ environ = {}
+ req = self._makeOne(environ)
+ del req.urlvars
+ self.assertEqual(req.urlvars, {})
+ self.assertEqual(environ['wsgiorg.routing_args'], ((), {}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlargs_getter_w_paste_key(self):
+ environ = {'paste.urlvars': {'foo': 'bar'},
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.urlargs, ())
+
+ def test_urlargs_getter_w_wsgiorg_key(self):
+ environ = {'wsgiorg.routing_args': (('a', 'b'), {'foo': 'bar'}),
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.urlargs, ('a', 'b'))
+
+ def test_urlargs_getter_wo_keys(self):
+ environ = {}
+ req = self._makeOne(environ)
+ self.assertEqual(req.urlargs, ())
+ self.assert_('wsgiorg.routing_args' not in environ)
+
+ def test_urlargs_setter_w_paste_key(self):
+ environ = {'paste.urlvars': {'foo': 'bar'},
+ }
+ req = self._makeOne(environ)
+ req.urlargs = ('a', 'b')
+ self.assertEqual(req.urlargs, ('a', 'b'))
+ self.assertEqual(environ['wsgiorg.routing_args'],
+ (('a', 'b'), {'foo': 'bar'}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlargs_setter_w_wsgiorg_key(self):
+ environ = {'wsgiorg.routing_args': ((), {'foo': 'bar'}),
+ }
+ req = self._makeOne(environ)
+ req.urlargs = ('a', 'b')
+ self.assertEqual(req.urlargs, ('a', 'b'))
+ self.assertEqual(environ['wsgiorg.routing_args'],
+ (('a', 'b'), {'foo': 'bar'}))
+
+ def test_urlargs_setter_wo_keys(self):
+ environ = {}
+ req = self._makeOne(environ)
+ req.urlargs = ('a', 'b')
+ self.assertEqual(req.urlargs, ('a', 'b'))
+ self.assertEqual(environ['wsgiorg.routing_args'],
+ (('a', 'b'), {}))
+ self.assert_('paste.urlvars' not in environ)
+
+ def test_urlargs_deleter_w_wsgiorg_key(self):
+ environ = {'wsgiorg.routing_args': (('a', 'b'), {'foo': 'bar'}),
+ }
+ req = self._makeOne(environ)
+ del req.urlargs
+ self.assertEqual(req.urlargs, ())
+ self.assertEqual(environ['wsgiorg.routing_args'],
+ ((), {'foo': 'bar'}))
+
+ def test_urlargs_deleter_w_wsgiorg_key_empty(self):
+ environ = {'wsgiorg.routing_args': ((), {}),
+ }
+ req = self._makeOne(environ)
+ del req.urlargs
+ self.assertEqual(req.urlargs, ())
+ self.assert_('paste.urlvars' not in environ)
+ self.assert_('wsgiorg.routing_args' not in environ)
+
+ def test_urlargs_deleter_wo_keys(self):
+ environ = {}
+ req = self._makeOne(environ)
+ del req.urlargs
+ self.assertEqual(req.urlargs, ())
+ self.assert_('paste.urlvars' not in environ)
+ self.assert_('wsgiorg.routing_args' not in environ)
+
+ def test_str_cookies_empty_environ(self):
+ req = self._makeOne({})
+ self.assertEqual(req.str_cookies, {})
+
+ def test_str_cookies_w_webob_parsed_cookies_matching_source(self):
+ environ = {
+ 'HTTP_COOKIE': 'a=b',
+ 'webob._parsed_cookies': ('a=b', {'a': 'b'}),
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.str_cookies, {'a': 'b'})
+
+ def test_str_cookies_w_webob_parsed_cookies_mismatched_source(self):
+ environ = {
+ 'HTTP_COOKIE': 'a=b',
+ 'webob._parsed_cookies': ('a=b;c=d', {'a': 'b', 'c': 'd'}),
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.str_cookies, {'a': 'b'})
+
+ def test_is_xhr_no_header(self):
+ req = self._makeOne({})
+ self.assert_(not req.is_xhr)
+
+ def test_is_xhr_header_miss(self):
+ environ = {'HTTP_X_REQUESTED_WITH': 'notAnXMLHTTPRequest'}
+ req = self._makeOne(environ)
+ self.assert_(not req.is_xhr)
+
+ def test_is_xhr_header_hit(self):
+ environ = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+ req = self._makeOne(environ)
+ self.assert_(req.is_xhr)
+
+ # host
+ def test_host_getter_w_HTTP_HOST(self):
+ environ = {'HTTP_HOST': 'example.com:8888'}
+ req = self._makeOne(environ)
+ self.assertEqual(req.host, 'example.com:8888')
+
+ def test_host_getter_wo_HTTP_HOST(self):
+ environ = {'SERVER_NAME': 'example.com',
+ 'SERVER_PORT': '8888'}
+ req = self._makeOne(environ)
+ self.assertEqual(req.host, 'example.com:8888')
+
+ def test_host_setter(self):
+ environ = {}
+ req = self._makeOne(environ)
+ req.host = 'example.com:8888'
+ self.assertEqual(environ['HTTP_HOST'], 'example.com:8888')
+
+ def test_host_deleter_hit(self):
+ environ = {'HTTP_HOST': 'example.com:8888'}
+ req = self._makeOne(environ)
+ del req.host
+ self.assert_('HTTP_HOST' not in environ)
+
+ def test_host_deleter_miss(self):
+ environ = {}
+ req = self._makeOne(environ)
+ del req.host # doesn't raise
+
+ # body
+ def test_body_getter(self):
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'webob.is_body_seekable': True,
+ 'CONTENT_LENGTH': len('input'),
+ }
+ req = self._makeOne(environ)
+ self.assertEqual(req.body, 'input')
+ self.assertEqual(req.content_length, len('input'))
+ def test_body_setter_None(self):
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'webob.is_body_seekable': True,
+ 'CONTENT_LENGTH': len('input'),
+ }
+ req = self._makeOne(environ)
+ req.body = None
+ self.assertEqual(req.body, '')
+ self.assertEqual(req.content_length, 0)
+ self.assert_(req.is_body_seekable)
+ def test_body_setter_non_string_raises(self):
+ req = self._makeOne({})
+ def _test():
+ req.body = object()
+ self.assertRaises(TypeError, _test)
+ def test_body_setter_value(self):
+ BEFORE = self._makeStringIO('before')
+ environ = {'wsgi.input': BEFORE,
+ 'webob.is_body_seekable': True,
+ 'CONTENT_LENGTH': len('before'),
+ }
+ req = self._makeOne(environ)
+ req.body = 'after'
+ self.assertEqual(req.body, 'after')
+ self.assertEqual(req.content_length, len('after'))
+ self.assert_(req.is_body_seekable)
+ def test_body_deleter_None(self):
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'webob.is_body_seekable': True,
+ 'CONTENT_LENGTH': len('input'),
+ }
+ req = self._makeOne(environ)
+ del req.body
+ self.assertEqual(req.body, '')
+ self.assertEqual(req.content_length, 0)
+ self.assert_(req.is_body_seekable)
+
+ def test_str_POST_not_POST_or_PUT(self):
+ from webob.multidict import NoVars
+ environ = {'REQUEST_METHOD': 'GET',
+ }
+ req = self._makeOne(environ)
+ result = req.str_POST
+ self.assert_(isinstance(result, NoVars))
+ self.assert_(result.reason.startswith('Not a form request'))
+
+ def test_str_POST_existing_cache_hit(self):
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'REQUEST_METHOD': 'POST',
+ 'webob._parsed_post_vars': ({'foo': 'bar'}, INPUT),
+ }
+ req = self._makeOne(environ)
+ result = req.str_POST
+ self.assertEqual(result, {'foo': 'bar'})
+
+ def test_str_PUT_missing_content_type(self):
+ from webob.multidict import NoVars
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'REQUEST_METHOD': 'PUT',
+ }
+ req = self._makeOne(environ)
+ result = req.str_POST
+ self.assert_(isinstance(result, NoVars))
+ self.assert_(result.reason.startswith('Not an HTML form submission'))
+
+ def test_str_PUT_bad_content_type(self):
+ from webob.multidict import NoVars
+ INPUT = self._makeStringIO('input')
+ environ = {'wsgi.input': INPUT,
+ 'REQUEST_METHOD': 'PUT',
+ 'CONTENT_TYPE': 'text/plain',
+ }
+ req = self._makeOne(environ)
+ result = req.str_POST
+ self.assert_(isinstance(result, NoVars))
+ self.assert_(result.reason.startswith('Not an HTML form submission'))
+
+ def test_str_POST_multipart(self):
+ BODY_TEXT = (
+ '------------------------------deb95b63e42a\n'
+ 'Content-Disposition: form-data; name="foo"\n'
+ '\n'
+ 'foo\n'
+ '------------------------------deb95b63e42a\n'
+ 'Content-Disposition: form-data; name="bar"; filename="bar.txt"\n'
+ 'Content-type: application/octet-stream\n'
+ '\n'
+ 'these are the contents of the file "bar.txt"\n'
+ '\n'
+ '------------------------------deb95b63e42a--\n')
+ INPUT = self._makeStringIO(BODY_TEXT)
+ environ = {'wsgi.input': INPUT,
+ 'webob.is_body_seekable': True,
+ 'REQUEST_METHOD': 'POST',
+ 'CONTENT_TYPE': 'multipart/form-data; '
+ 'boundary=----------------------------deb95b63e42a',
+ 'CONTENT_LENGTH': len(BODY_TEXT),
+ }
+ req = self._makeOne(environ)
+ result = req.str_POST
+ self.assertEqual(result['foo'], 'foo')
+ bar = result['bar']
+ self.assertEqual(bar.name, 'bar')
+ self.assertEqual(bar.filename, 'bar.txt')
+ self.assertEqual(bar.file.read(),
+ 'these are the contents of the file "bar.txt"\n')
+
+ # POST
+ # str_GET
+ def test_str_GET_reflects_query_string(self):
+ environ = {
+ 'QUERY_STRING': 'foo=123',
+ }
+ req = self._makeOne(environ)
+ result = req.str_GET
+ self.assertEqual(result, {'foo': '123'})
+ req.query_string = 'foo=456'
+ result = req.str_GET
+ self.assertEqual(result, {'foo': '456'})
+ req.query_string = ''
+ result = req.str_GET
+ self.assertEqual(result, {})
+
+ def test_str_GET_updates_query_string(self):
+ environ = {
+ }
+ req = self._makeOne(environ)
+ result = req.query_string
+ self.assertEqual(result, '')
+ req.str_GET['foo'] = '123'
+ result = req.query_string
+ self.assertEqual(result, 'foo=123')
+ del req.str_GET['foo']
+ result = req.query_string
+ self.assertEqual(result, '')
+
+ # GET
+ # str_postvars
+ # postvars
+ # str_queryvars
+ # queryvars
+ # is_xhr
+ # str_params
+ # params
+
+ def test_str_cookies_wo_webob_parsed_cookies(self):
+ from webob import Request
+ environ = {
+ 'HTTP_COOKIE': 'a=b',
+ }
+ req = Request.blank('/', environ)
+ self.assertEqual(req.str_cookies, {'a': 'b'})
+
+ # cookies
+ # copy
+
+ def test_copy_get(self):
+ from webob import Request
+ environ = {
+ 'HTTP_COOKIE': 'a=b',
+ }
+ req = Request.blank('/', environ)
+ clone = req.copy_get()
+ for k, v in req.environ.items():
+ if k in ('CONTENT_LENGTH', 'webob.is_body_seekable'):
+ self.assert_(k not in clone.environ)
+ elif k == 'wsgi.input':
+ self.assert_(clone.environ[k] is not v)
+ else:
+ self.assertEqual(clone.environ[k], v)
+
+ def test_remove_conditional_headers_accept_encoding(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.accept_encoding='gzip,deflate'
+ req.remove_conditional_headers()
+ self.assertEqual(bool(req.accept_encoding), False)
+
+ def test_remove_conditional_headers_if_modified_since(self):
+ from datetime import datetime
+ from webob import Request, UTC
+ req = Request.blank('/')
+ req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ req.remove_conditional_headers()
+ self.assertEqual(req.if_modified_since, None)
+
+ def test_remove_conditional_headers_if_none_match(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.if_none_match = 'foo, bar'
+ req.remove_conditional_headers()
+ self.assertEqual(bool(req.if_none_match), False)
+
+ def test_remove_conditional_headers_if_range(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.if_range = 'foo, bar'
+ req.remove_conditional_headers()
+ self.assertEqual(bool(req.if_range), False)
+
+ def test_remove_conditional_headers_range(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.range = 'bytes=0-100'
+ req.remove_conditional_headers()
+ self.assertEqual(req.range, None)
+
+
+ # is_body_seekable
+ # make_body_seekable
+ # copy_body
+ # make_tempfile
+ # remove_conditional_headers
+ # accept
+ # accept_charset
+ # accept_encoding
+ # accept_language
+ # authorization
+
+ # cache_control
+ def test_cache_control_reflects_environ(self):
+ environ = {
+ 'HTTP_CACHE_CONTROL': 'max-age=5',
+ }
+ req = self._makeOne(environ)
+ result = req.cache_control
+ self.assertEqual(result.properties, {'max-age': 5})
+ req.environ.update(HTTP_CACHE_CONTROL='max-age=10')
+ result = req.cache_control
+ self.assertEqual(result.properties, {'max-age': 10})
+ req.environ.update(HTTP_CACHE_CONTROL='')
+ result = req.cache_control
+ self.assertEqual(result.properties, {})
+
+ def test_cache_control_updates_environ(self):
+ environ = {}
+ req = self._makeOne(environ)
+ req.cache_control.max_age = 5
+ result = req.environ['HTTP_CACHE_CONTROL']
+ self.assertEqual(result, 'max-age=5')
+ req.cache_control.max_age = 10
+ result = req.environ['HTTP_CACHE_CONTROL']
+ self.assertEqual(result, 'max-age=10')
+ req.cache_control = None
+ result = req.environ['HTTP_CACHE_CONTROL']
+ self.assertEqual(result, '')
+ del req.cache_control
+ self.assert_('HTTP_CACHE_CONTROL' not in req.environ)
+
+ def test_cache_control_set_dict(self):
+ environ = {}
+ req = self._makeOne(environ)
+ req.cache_control = {'max-age': 5}
+ result = req.cache_control
+ self.assertEqual(result.max_age, 5)
+
+ def test_cache_control_set_object(self):
+ from webob.cachecontrol import CacheControl
+ environ = {}
+ req = self._makeOne(environ)
+ req.cache_control = CacheControl({'max-age': 5}, type='request')
+ result = req.cache_control
+ self.assertEqual(result.max_age, 5)
+
+ def test_cache_control_gets_cached(self):
+ environ = {}
+ req = self._makeOne(environ)
+ self.assert_(req.cache_control is req.cache_control)
+
+ #if_match
+ #if_none_match
+
+ #date
+ #if_modified_since
+ #if_unmodified_since
+ #if_range
+ #max_forwards
+ #pragma
+ #range
+ #referer
+ #referrer
+ #user_agent
+ #__repr__
+ #__str__
+ #from_file
+
+ #call_application
+ def test_call_application_calls_application(self):
+ environ = {}
+ req = self._makeOne(environ)
+ def application(environ, start_response):
+ start_response('200 OK', [('content-type', 'text/plain')])
+ return ['...\n']
+ status, headers, output = req.call_application(application)
+ self.assertEqual(status, '200 OK')
+ self.assertEqual(headers, [('content-type', 'text/plain')])
+ self.assertEqual(''.join(output), '...\n')
+
+ def test_call_application_provides_write(self):
+ environ = {}
+ req = self._makeOne(environ)
+ def application(environ, start_response):
+ write = start_response('200 OK', [('content-type', 'text/plain')])
+ write('...\n')
+ return []
+ status, headers, output = req.call_application(application)
+ self.assertEqual(status, '200 OK')
+ self.assertEqual(headers, [('content-type', 'text/plain')])
+ self.assertEqual(''.join(output), '...\n')
+
+ def test_call_application_closes_iterable_when_mixed_with_write_calls(self):
+ environ = {
+ 'test._call_application_called_close': False
+ }
+ req = self._makeOne(environ)
+ def application(environ, start_response):
+ write = start_response('200 OK', [('content-type', 'text/plain')])
+ class AppIter(object):
+ def __iter__(self):
+ yield '...\n'
+ def close(self):
+ environ['test._call_application_called_close'] = True
+ write('...\n')
+ return AppIter()
+ status, headers, output = req.call_application(application)
+ self.assertEqual(''.join(output), '...\n...\n')
+ self.assertEqual(environ['test._call_application_called_close'], True)
+
+ def test_call_application_raises_exc_info(self):
+ environ = {}
+ req = self._makeOne(environ)
+ def application(environ, start_response):
+ try:
+ raise RuntimeError('OH NOES')
+ except:
+ import sys
+ exc_info = sys.exc_info()
+ start_response('200 OK', [('content-type', 'text/plain')], exc_info)
+ return ['...\n']
+ self.assertRaises(RuntimeError, req.call_application, application)
+
+ def test_call_application_returns_exc_info(self):
+ environ = {}
+ req = self._makeOne(environ)
+ def application(environ, start_response):
+ try:
+ raise RuntimeError('OH NOES')
+ except:
+ import sys
+ exc_info = sys.exc_info()
+ start_response('200 OK', [('content-type', 'text/plain')], exc_info)
+ return ['...\n']
+ status, headers, output, exc_info = req.call_application(application, True)
+ self.assertEqual(status, '200 OK')
+ self.assertEqual(headers, [('content-type', 'text/plain')])
+ self.assertEqual(''.join(output), '...\n')
+ self.assertEqual(exc_info[0], RuntimeError)
+
+ #get_response
+ #blank
+
+ #from_string
+ def test_from_string_extra_data(self):
+ from webob import BaseRequest
+ _test_req_copy = _test_req.replace('Content-Type',
+ 'Content-Length: 337\r\nContent-Type')
+ self.assertRaises(ValueError, BaseRequest.from_string,
+ _test_req_copy+'EXTRA!')
+
+ #as_string
+ def test_as_string_skip_body(self):
+ from webob import BaseRequest
+ req = BaseRequest.from_string(_test_req)
+ body = req.as_string(skip_body=True)
+ self.assertEqual(body.count('\r\n\r\n'), 0)
+ self.assertEqual(req.as_string(skip_body=337), req.as_string())
+ body = req.as_string(337-1).split('\r\n\r\n', 1)[1]
+ self.assertEqual(body, '<body skipped (len=337)>')
+
+ def test_adhoc_attrs_set(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.foo = 1
+ self.assertEqual(req.environ['webob.adhoc_attrs'], {'foo': 1})
+
+ def test_adhoc_attrs_set_nonadhoc(self):
+ from webob import Request
+ req = Request.blank('/', environ={'webob.adhoc_attrs':{}})
+ req.request_body_tempfile_limit = 1
+ self.assertEqual(req.environ['webob.adhoc_attrs'], {})
+
+ def test_adhoc_attrs_get(self):
+ from webob import Request
+ req = Request.blank('/', environ={'webob.adhoc_attrs': {'foo': 1}})
+ self.assertEqual(req.foo, 1)
+
+ def test_adhoc_attrs_get_missing(self):
+ from webob import Request
+ req = Request.blank('/')
+ self.assertRaises(AttributeError, getattr, req, 'some_attr')
+
+ def test_adhoc_attrs_del(self):
+ from webob import Request
+ req = Request.blank('/', environ={'webob.adhoc_attrs': {'foo': 1}})
+ del req.foo
+ self.assertEqual(req.environ['webob.adhoc_attrs'], {})
+
+ def test_adhoc_attrs_del_missing(self):
+ from webob import Request
+ req = Request.blank('/')
+ self.assertRaises(AttributeError, delattr, req, 'some_attr')
+
+ # TODO: webob/request.py:1143
+ # the only known way to reach this line is by experimentation
+
+
+class RequestTests_functional(unittest.TestCase):
+
+ def test_gets(self):
+ from webtest import TestApp
+ app = TestApp(simpleapp)
+ res = app.get('/')
+ self.assert_('Hello' in res)
+ self.assert_("get is GET([])" in res)
+ self.assert_("post is <NoVars: Not a form request>" in res)
+
+ res = app.get('/?name=george')
+ res.mustcontain("get is GET([('name', 'george')])")
+ res.mustcontain("Val is george")
+
+ def test_language_parsing(self):
+ from webtest import TestApp
+ app = TestApp(simpleapp)
+ res = app.get('/')
+ self.assert_("The languages are: ['en-US']" in res)
+
+ res = app.get('/',
+ headers={'Accept-Language': 'da, en-gb;q=0.8, en;q=0.7'})
+ self.assert_("languages are: ['da', 'en-gb', 'en-US']" in res)
+
+ res = app.get('/',
+ headers={'Accept-Language': 'en-gb;q=0.8, da, en;q=0.7'})
+ self.assert_("languages are: ['da', 'en-gb', 'en-US']" in res)
+
+ def test_mime_parsing(self):
+ from webtest import TestApp
+ app = TestApp(simpleapp)
+ res = app.get('/', headers={'Accept':'text/html'})
+ self.assert_("accepttypes is: text/html" in res)
+
+ res = app.get('/', headers={'Accept':'application/xml'})
+ self.assert_("accepttypes is: application/xml" in res)
+
+ res = app.get('/', headers={'Accept':'application/xml,*/*'})
+ self.assert_("accepttypes is: application/xml" in res)
+
+ def test_accept_best_match(self):
+ from webob import Request
+ self.assert_(not Request.blank('/').accept)
+ self.assert_(not Request.blank('/', headers={'Accept': ''}).accept)
+ req = Request.blank('/', headers={'Accept':'text/plain'})
+ self.assert_(req.accept)
+ self.assertRaises(ValueError, req.accept.best_match, ['*/*'])
+ req = Request.blank('/', accept=['*/*','text/*'])
+ self.assertEqual(
+ req.accept.best_match(['application/x-foo', 'text/plain']),
+ 'text/plain')
+ self.assertEqual(
+ req.accept.best_match(['text/plain', 'application/x-foo']),
+ 'text/plain')
+ req = Request.blank('/', accept=['text/plain', 'message/*'])
+ self.assertEqual(
+ req.accept.best_match(['message/x-foo', 'text/plain']),
+ 'text/plain')
+ self.assertEqual(
+ req.accept.best_match(['text/plain', 'message/x-foo']),
+ 'text/plain')
+
+ def test_from_mimeparse(self):
+ # http://mimeparse.googlecode.com/svn/trunk/mimeparse.py
+ from webob import Request
+ supported = ['application/xbel+xml', 'application/xml']
+ tests = [('application/xbel+xml', 'application/xbel+xml'),
+ ('application/xbel+xml; q=1', 'application/xbel+xml'),
+ ('application/xml; q=1', 'application/xml'),
+ ('application/*; q=1', 'application/xbel+xml'),
+ ('*/*', 'application/xbel+xml')]
+
+ for accept, get in tests:
+ req = Request.blank('/', headers={'Accept':accept})
+ self.assertEqual(req.accept.best_match(supported), get)
+
+ supported = ['application/xbel+xml', 'text/xml']
+ tests = [('text/*;q=0.5,*/*; q=0.1', 'text/xml'),
+ ('text/html,application/atom+xml; q=0.9', None)]
+
+ for accept, get in tests:
+ req = Request.blank('/', headers={'Accept':accept})
+ self.assertEqual(req.accept.best_match(supported), get)
+
+ supported = ['application/json', 'text/html']
+ tests = [
+ ('application/json, text/javascript, */*', 'application/json'),
+ ('application/json, text/html;q=0.9', 'application/json'),
+ ]
+
+ for accept, get in tests:
+ req = Request.blank('/', headers={'Accept':accept})
+ self.assertEqual(req.accept.best_match(supported), get)
+
+ offered = ['image/png', 'application/xml']
+ tests = [
+ ('image/png', 'image/png'),
+ ('image/*', 'image/png'),
+ ('image/*, application/xml', 'application/xml'),
+ ]
+
+ for accept, get in tests:
+ req = Request.blank('/', accept=accept)
+ self.assertEqual(req.accept.best_match(offered), get)
+
+ def test_headers(self):
+ from webtest import TestApp
+ app = TestApp(simpleapp)
+ headers = {
+ 'If-Modified-Since': 'Sat, 29 Oct 1994 19:43:31 GMT',
+ 'Cookie': 'var1=value1',
+ 'User-Agent': 'Mozilla 4.0 (compatible; MSIE)',
+ 'If-None-Match': '"etag001", "etag002"',
+ 'X-Requested-With': 'XMLHttpRequest',
+ }
+ res = app.get('/?foo=bar&baz', headers=headers)
+ res.mustcontain(
+ 'if_modified_since: ' +
+ 'datetime.datetime(1994, 10, 29, 19, 43, 31, tzinfo=UTC)',
+ "user_agent: 'Mozilla",
+ 'is_xhr: True',
+ "cookies is {'var1': 'value1'}",
+ "params is NestedMultiDict([('foo', 'bar'), ('baz', '')])",
+ "if_none_match: <ETag etag001 or etag002>",
+ )
+
+ def test_bad_cookie(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.headers['Cookie'] = '070-it-:><?0'
+ self.assertEqual(req.cookies, {})
+ req.headers['Cookie'] = 'foo=bar'
+ self.assertEqual(req.cookies, {'foo': 'bar'})
+ req.headers['Cookie'] = '...'
+ self.assertEqual(req.cookies, {})
+ req.headers['Cookie'] = '=foo'
+ self.assertEqual(req.cookies, {})
+ req.headers['Cookie'] = ('dismiss-top=6; CP=null*; '
+ 'PHPSESSID=0a539d42abc001cdc762809248d4beed; a=42')
+ self.assertEqual(req.cookies, {
+ 'CP': u'null*',
+ 'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
+ 'a': u'42',
+ 'dismiss-top': u'6'
+ })
+ req.headers['Cookie'] = 'fo234{=bar blub=Blah'
+ self.assertEqual(req.cookies, {'blub': 'Blah'})
+
+ def test_cookie_quoting(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.headers['Cookie'] = 'foo="?foo"; Path=/'
+ self.assertEqual(req.cookies, {'foo': '?foo'})
+
+ def test_path_quoting(self):
+ from webob import Request
+ path = '/:@&+$,/bar'
+ req = Request.blank(path)
+ self.assertEqual(req.path, path)
+ self.assert_(req.url.endswith(path))
+
+ def test_params(self):
+ from webob import Request
+ req = Request.blank('/?a=1&b=2')
+ req.method = 'POST'
+ req.body = 'b=3'
+ self.assertEqual(req.params.items(),
+ [('a', '1'), ('b', '2'), ('b', '3')])
+ new_params = req.params.copy()
+ self.assertEqual(new_params.items(),
+ [('a', '1'), ('b', '2'), ('b', '3')])
+ new_params['b'] = '4'
+ self.assertEqual(new_params.items(), [('a', '1'), ('b', '4')])
+ # The key name is \u1000:
+ req = Request.blank('/?%E1%80%80=x',
+ decode_param_names=True, charset='UTF-8')
+ self.assert_(req.decode_param_names)
+ self.assert_(u'\u1000' in req.GET.keys())
+ self.assertEqual(req.GET[u'\u1000'], 'x')
+
+ def test_copy_body(self):
+ from webob import Request
+ req = Request.blank('/', method='POST', body='some text',
+ request_body_tempfile_limit=1)
+ old_body_file = req.body_file_raw
+ req.copy_body()
+ self.assert_(req.body_file_raw is not old_body_file)
+ req = Request.blank('/', method='POST',
+ body_file=UnseekableInput('0123456789'), content_length=10)
+ self.assert_(not hasattr(req.body_file_raw, 'seek'))
+ old_body_file = req.body_file_raw
+ req.make_body_seekable()
+ self.assert_(req.body_file_raw is not old_body_file)
+ self.assertEqual(req.body, '0123456789')
+ old_body_file = req.body_file
+ req.make_body_seekable()
+ self.assert_(req.body_file_raw is old_body_file)
+ self.assert_(req.body_file is old_body_file)
+
+ def test_broken_seek(self):
+ # copy() should work even when the input has a broken seek method
+ from webob import Request
+ req = Request.blank('/', method='POST',
+ body_file=UnseekableInputWithSeek('0123456789'),
+ content_length=10)
+ self.assert_(hasattr(req.body_file_raw, 'seek'))
+ self.assertRaises(IOError, req.body_file_raw.seek, 0)
+ old_body_file = req.body_file
+ req2 = req.copy()
+ self.assert_(req2.body_file_raw is req2.body_file is not old_body_file)
+ self.assertEqual(req2.body, '0123456789')
+
+ def test_set_body(self):
+ from webob import BaseRequest
+ req = BaseRequest.blank('/', body='foo')
+ self.assert_(req.is_body_seekable)
+ self.assertEqual(req.body, 'foo')
+ self.assertEqual(req.content_length, 3)
+ del req.body
+ self.assertEqual(req.body, '')
+ self.assertEqual(req.content_length, 0)
+
+ def test_broken_clen_header(self):
+ # if the UA sends "content_length: ..' header (the name is wrong)
+ # it should not break the req.headers.items()
+ from webob import Request
+ req = Request.blank('/')
+ req.environ['HTTP_CONTENT_LENGTH'] = '0'
+ req.headers.items()
+
+ def test_nonstr_keys(self):
+ # non-string env keys shouldn't break req.headers
+ from webob import Request
+ req = Request.blank('/')
+ req.environ[1] = 1
+ req.headers.items()
+
+
+ def test_authorization(self):
+ from webob import Request
+ req = Request.blank('/')
+ req.authorization = 'Digest uri="/?a=b"'
+ self.assertEqual(req.authorization, ('Digest', {'uri': '/?a=b'}))
+
+ def test_authorization2(self):
+ from webob.descriptors import parse_auth_params
+ for s, d in [
+ ('x=y', {'x': 'y'}),
+ ('x="y"', {'x': 'y'}),
+ ('x=y,z=z', {'x': 'y', 'z': 'z'}),
+ ('x=y, z=z', {'x': 'y', 'z': 'z'}),
+ ('x="y",z=z', {'x': 'y', 'z': 'z'}),
+ ('x="y", z=z', {'x': 'y', 'z': 'z'}),
+ ('x="y,x", z=z', {'x': 'y,x', 'z': 'z'}),
+ ]:
+ self.assertEqual(parse_auth_params(s), d)
+
+
+ def test_from_file(self):
+ from webob import Request
+ req = Request.blank('http://example.com:8000/test.html?params')
+ self.equal_req(req)
+
+ req = Request.blank('http://example.com/test2')
+ req.method = 'POST'
+ req.body = 'test=example'
+ self.equal_req(req)
+
+ def test_req_kw_none_val(self):
+ from webob import Request
+ request = Request({}, content_length=None)
+ self.assert_('content-length' not in request.headers)
+ self.assert_('content-type' not in request.headers)
+
+ def test_env_keys(self):
+ from webob import Request
+ req = Request.blank('/')
+ # SCRIPT_NAME can be missing
+ del req.environ['SCRIPT_NAME']
+ self.assertEqual(req.script_name, '')
+ self.assertEqual(req.uscript_name, u'')
+
+ def test_repr_nodefault(self):
+ from webob.request import NoDefault
+ nd = NoDefault
+ self.assertEqual(repr(nd), '(No Default)')
+
+ def test_request_noenviron_param(self):
+ # Environ is a a mandatory not null param in Request.
+ from webob import Request
+ self.assertRaises(TypeError, Request, environ=None)
+
+ def test_environ_getter(self):
+ # Parameter environ_getter in Request is no longer valid and
+ # should raise an error in case it's used
+ from webob import Request
+ class env(object):
+ def __init__(self, env):
+ self.env = env
+ def env_getter(self):
+ return self.env
+ self.assertRaises(ValueError,
+ Request, environ_getter=env({'a':1}).env_getter)
+
+ def test_unicode_errors(self):
+ # Passing unicode_errors != NoDefault should assign value to
+ # dictionary['unicode_errors'], else not
+ from webob.request import NoDefault
+ from webob import Request
+ r = Request({'a':1}, unicode_errors='strict')
+ self.assert_('unicode_errors' in r.__dict__)
+ r = Request({'a':1}, unicode_errors=NoDefault)
+ self.assert_('unicode_errors' not in r.__dict__)
+
+ def test_charset_deprecation(self):
+ # Any class that inherits from BaseRequest cannot define a
+ # default_charset attribute.
+ # Any class that inherits from BaseRequest cannot define a
+ # charset attr that is instance of str
+ from webob import BaseRequest
+ from webob.request import AdhocAttrMixin
+ class NewRequest(BaseRequest):
+ default_charset = 'utf-8'
+ def __init__(self, environ, **kw):
+ super(NewRequest, self).__init__(environ, **kw)
+ self.assertRaises(DeprecationWarning, NewRequest, {'a':1})
+ class NewRequest(BaseRequest):
+ charset = 'utf-8'
+ def __init__(self, environ, **kw):
+ super(NewRequest, self).__init__(environ, **kw)
+ self.assertRaises(DeprecationWarning, NewRequest, {'a':1})
+ class NewRequest(AdhocAttrMixin, BaseRequest):
+ default_charset = 'utf-8'
+ def __init__(self, environ, **kw):
+ super(NewRequest, self).__init__(environ, **kw)
+ self.assertRaises(DeprecationWarning, NewRequest, {'a':1})
+ class NewRequest(AdhocAttrMixin, BaseRequest):
+ charset = 'utf-8'
+ def __init__(self, environ, **kw):
+ super(NewRequest, self).__init__(environ, **kw)
+ self.assertRaises(DeprecationWarning, NewRequest, {'a':1})
+
+ def test_unexpected_kw(self):
+ # Passed an attr in kw that does not exist in the class, should
+ # raise an error
+ # Passed an attr in kw that does exist in the class, should be ok
+ from webob import Request
+ self.assertRaises(TypeError,
+ Request, {'a':1}, this_does_not_exist=1)
+ r = Request({'a':1}, **{'charset':'utf-8', 'server_name':'127.0.0.1'})
+ self.assertEqual(getattr(r, 'charset', None), 'utf-8')
+ self.assertEqual(getattr(r, 'server_name', None), '127.0.0.1')
+
+ def test_body_file_setter(self):
+ # If body_file is passed and it's instance of str, we define
+ # environ['wsgi.input'] and content_length. Plus, while deleting the
+ # attribute, we should get '' and 0 respectively
+ from webob import Request
+ r = Request({'a':1}, **{'body_file':'hello world'})
+ self.assertEqual(r.environ['wsgi.input'].getvalue(), 'hello world')
+ self.assertEqual(int(r.environ['CONTENT_LENGTH']), len('hello world'))
+ del r.body_file
+ self.assertEqual(r.environ['wsgi.input'].getvalue(), '')
+ self.assertEqual(int(r.environ['CONTENT_LENGTH']), 0)
+
+ def test_conttype_set_del(self):
+ # Deleting content_type attr from a request should update the
+ # environ dict
+ # Assigning content_type should replace first option of the environ
+ # dict
+ from webob import Request
+ r = Request({'a':1}, **{'content_type':'text/html'})
+ self.assert_('CONTENT_TYPE' in r.environ)
+ self.assert_(hasattr(r, 'content_type'))
+ del r.content_type
+ self.assert_('CONTENT_TYPE' not in r.environ)
+ a = Request({'a':1},
+ content_type='charset=utf-8;application/atom+xml;type=entry')
+ self.assert_(a.environ['CONTENT_TYPE']==
+ 'charset=utf-8;application/atom+xml;type=entry')
+ a.content_type = 'charset=utf-8'
+ self.assert_(a.environ['CONTENT_TYPE']==
+ 'charset=utf-8;application/atom+xml;type=entry')
+
+ def test_headers2(self):
+ # Setting headers in init and later with a property, should update
+ # the info
+ from webob import Request
+ headers = {'Host': 'www.example.com',
+ 'Accept-Language': 'en-us,en;q=0.5',
+ 'Accept-Encoding': 'gzip,deflate',
+ 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ 'Keep-Alive': '115',
+ 'Connection': 'keep-alive',
+ 'Cache-Control': 'max-age=0'}
+ r = Request({'a':1}, headers=headers)
+ for i in headers.keys():
+ self.assert_(i in r.headers and
+ 'HTTP_'+i.upper().replace('-', '_') in r.environ)
+ r.headers = {'Server':'Apache'}
+ self.assertEqual(r.environ.keys(), ['a', 'HTTP_SERVER'])
+
+ def test_host_url(self):
+ # Request has a read only property host_url that combines several
+ # keys to create a host_url
+ from webob import Request
+ a = Request({'wsgi.url_scheme':'http'}, **{'host':'www.example.com'})
+ self.assertEqual(a.host_url, 'http://www.example.com')
+ a = Request({'wsgi.url_scheme':'http'}, **{'server_name':'localhost',
+ 'server_port':5000})
+ self.assertEqual(a.host_url, 'http://localhost:5000')
+ a = Request({'wsgi.url_scheme':'https'}, **{'server_name':'localhost',
+ 'server_port':443})
+ self.assertEqual(a.host_url, 'https://localhost')
+
+ def test_path_info_p(self):
+ # Peek path_info to see what's coming
+ # Pop path_info until there's nothing remaining
+ from webob import Request
+ a = Request({'a':1}, **{'path_info':'/foo/bar','script_name':''})
+ self.assertEqual(a.path_info_peek(), 'foo')
+ self.assertEqual(a.path_info_pop(), 'foo')
+ self.assertEqual(a.path_info_peek(), 'bar')
+ self.assertEqual(a.path_info_pop(), 'bar')
+ self.assertEqual(a.path_info_peek(), None)
+ self.assertEqual(a.path_info_pop(), None)
+
+ def test_urlvars_property(self):
+ # Testing urlvars setter/getter/deleter
+ from webob import Request
+ a = Request({'wsgiorg.routing_args':((),{'x':'y'}),
+ 'paste.urlvars':{'test':'value'}})
+ a.urlvars = {'hello':'world'}
+ self.assert_('paste.urlvars' not in a.environ)
+ self.assertEqual(a.environ['wsgiorg.routing_args'],
+ ((), {'hello':'world'}))
+ del a.urlvars
+ self.assert_('wsgiorg.routing_args' not in a.environ)
+ a = Request({'paste.urlvars':{'test':'value'}})
+ self.assertEqual(a.urlvars, {'test':'value'})
+ a.urlvars = {'hello':'world'}
+ self.assertEqual(a.environ['paste.urlvars'], {'hello':'world'})
+ del a.urlvars
+ self.assert_('paste.urlvars' not in a.environ)
+
+ def test_urlargs_property(self):
+ # Testing urlargs setter/getter/deleter
+ from webob import Request
+ a = Request({'paste.urlvars':{'test':'value'}})
+ self.assertEqual(a.urlargs, ())
+ a.urlargs = {'hello':'world'}
+ self.assertEqual(a.environ['wsgiorg.routing_args'],
+ ({'hello':'world'}, {'test':'value'}))
+ a = Request({'a':1})
+ a.urlargs = {'hello':'world'}
+ self.assertEqual(a.environ['wsgiorg.routing_args'],
+ ({'hello':'world'}, {}))
+ del a.urlargs
+ self.assert_('wsgiorg.routing_args' not in a.environ)
+
+ def test_host_property(self):
+ # Testing host setter/getter/deleter
+ from webob import Request
+ a = Request({'wsgi.url_scheme':'http'}, server_name='localhost',
+ server_port=5000)
+ self.assertEqual(a.host, "localhost:5000")
+ a.host = "localhost:5000"
+ self.assert_('HTTP_HOST' in a.environ)
+ del a.host
+ self.assert_('HTTP_HOST' not in a.environ)
+
+ def test_body_property(self):
+ # Testing body setter/getter/deleter plus making sure body has a
+ # seek method
+ #a = Request({'a':1}, **{'CONTENT_LENGTH':'?'})
+ # I cannot think of a case where somebody would put anything else
+ # than a # numerical value in CONTENT_LENGTH, Google didn't help
+ # either
+ #self.assertEqual(a.body, '')
+ # I need to implement a not seekable stringio like object.
+ import string
+ from webob import Request
+ from webob import BaseRequest
+ from cStringIO import StringIO
+ class DummyIO(object):
+ def __init__(self, txt):
+ self.txt = txt
+ def read(self, n=-1):
+ return self.txt[0:n]
+ limit = BaseRequest.request_body_tempfile_limit
+ len_strl = limit // len(string.letters) + 1
+ r = Request({'a':1}, body_file=DummyIO(string.letters * len_strl))
+ self.assertEqual(len(r.body), len(string.letters*len_strl)-1)
+ self.assertRaises(TypeError,
+ setattr, r, 'body', unicode('hello world'))
+ r.body = None
+ self.assertEqual(r.body, '')
+ r = Request({'a':1}, body_file=DummyIO(string.letters))
+ self.assert_(not hasattr(r.body_file_raw, 'seek'))
+ r.make_body_seekable()
+ self.assert_(hasattr(r.body_file_raw, 'seek'))
+ r = Request({'a':1}, body_file=StringIO(string.letters))
+ self.assert_(hasattr(r.body_file_raw, 'seek'))
+ r.make_body_seekable()
+ self.assert_(hasattr(r.body_file_raw, 'seek'))
+
+ def test_repr_invalid(self):
+ # If we have an invalid WSGI environ, the repr should tell us.
+ from webob import BaseRequest
+ req = BaseRequest({'CONTENT_LENGTH':'0', 'body':''})
+ self.assert_(repr(req).endswith('(invalid WSGI environ)>'))
+
+ def test_from_garbage_file(self):
+ # If we pass a file with garbage to from_file method it should
+ # raise an error plus missing bits in from_file method
+ from cStringIO import StringIO
+ from webob import BaseRequest
+ self.assertRaises(ValueError,
+ BaseRequest.from_file, StringIO('hello world'))
+ val_file = StringIO(
+ "GET /webob/ HTTP/1.1\n"
+ "Host: pythonpaste.org\n"
+ "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13)"
+ "Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13\n"
+ "Accept: "
+ "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;"
+ "q=0.8\n"
+ "Accept-Language: en-us,en;q=0.5\n"
+ "Accept-Encoding: gzip,deflate\n"
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n"
+ # duplicate on purpose
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n"
+ "Keep-Alive: 115\n"
+ "Connection: keep-alive\n"
+ )
+ req = BaseRequest.from_file(val_file)
+ self.assert_(isinstance(req, BaseRequest))
+ self.assert_(not repr(req).endswith('(invalid WSGI environ)>'))
+ val_file = StringIO(
+ "GET /webob/ HTTP/1.1\n"
+ "Host pythonpaste.org\n"
+ )
+ self.assertRaises(ValueError, BaseRequest.from_file, val_file)
+
+ def test_from_string(self):
+ # A valid request without a Content-Length header should still read
+ # the full body.
+ # Also test parity between as_string and from_string / from_file.
+ import cgi
+ from webob import BaseRequest
+ req = BaseRequest.from_string(_test_req)
+ self.assert_(isinstance(req, BaseRequest))
+ self.assert_(not repr(req).endswith('(invalid WSGI environ)>'))
+ self.assert_('\n' not in req.http_version or '\r' in req.http_version)
+ self.assert_(',' not in req.host)
+ self.assert_(req.content_length is not None)
+ self.assertEqual(req.content_length, 337)
+ self.assert_('foo' in req.body)
+ bar_contents = "these are the contents of the file 'bar.txt'\r\n"
+ self.assert_(bar_contents in req.body)
+ self.assertEqual(req.params['foo'], 'foo')
+ bar = req.params['bar']
+ self.assert_(isinstance(bar, cgi.FieldStorage))
+ self.assertEqual(bar.type, 'application/octet-stream')
+ bar.file.seek(0)
+ self.assertEqual(bar.file.read(), bar_contents)
+ # out should equal contents, except for the Content-Length header,
+ # so insert that.
+ _test_req_copy = _test_req.replace('Content-Type',
+ 'Content-Length: 337\r\nContent-Type')
+ self.assertEqual(str(req), _test_req_copy)
+
+ req2 = BaseRequest.from_string(_test_req2)
+ self.assert_('host' not in req2.headers)
+ self.assertEqual(str(req2), _test_req2.rstrip())
+ self.assertRaises(ValueError,
+ BaseRequest.from_string, _test_req2 + 'xx')
+
+ def test_blank(self):
+ # BaseRequest.blank class method
+ from webob import BaseRequest
+ self.assertRaises(ValueError, BaseRequest.blank,
+ 'www.example.com/foo?hello=world', None,
+ 'www.example.com/foo?hello=world')
+ self.assertRaises(ValueError, BaseRequest.blank,
+ 'gopher.example.com/foo?hello=world', None,
+ 'gopher://gopher.example.com')
+ req = BaseRequest.blank('www.example.com/foo?hello=world', None,
+ 'http://www.example.com')
+ self.assertEqual(req.environ.get('HTTP_HOST', None),
+ 'www.example.com:80')
+ self.assertEqual(req.environ.get('PATH_INFO', None),
+ 'www.example.com/foo')
+ self.assertEqual(req.environ.get('QUERY_STRING', None),
+ 'hello=world')
+ self.assertEqual(req.environ.get('REQUEST_METHOD', None), 'GET')
+ req = BaseRequest.blank('www.example.com/secure?hello=world', None,
+ 'https://www.example.com/secure')
+ self.assertEqual(req.environ.get('HTTP_HOST', None),
+ 'www.example.com:443')
+ self.assertEqual(req.environ.get('PATH_INFO', None),
+ 'www.example.com/secure')
+ self.assertEqual(req.environ.get('QUERY_STRING', None), 'hello=world')
+ self.assertEqual(req.environ.get('REQUEST_METHOD', None), 'GET')
+ self.assertEqual(req.environ.get('SCRIPT_NAME', None), '/secure')
+ self.assertEqual(req.environ.get('SERVER_NAME', None),
+ 'www.example.com')
+ self.assertEqual(req.environ.get('SERVER_PORT', None), '443')
+
+ def test_environ_from_url(self):
+ # Generating an environ just from an url plus testing environ_add_POST
+ from webob.request import environ_add_POST
+ from webob.request import environ_from_url
+ self.assertRaises(TypeError, environ_from_url,
+ 'http://www.example.com/foo?bar=baz#qux')
+ self.assertRaises(TypeError, environ_from_url,
+ 'gopher://gopher.example.com')
+ req = environ_from_url('http://www.example.com/foo?bar=baz')
+ self.assertEqual(req.get('HTTP_HOST', None), 'www.example.com:80')
+ self.assertEqual(req.get('PATH_INFO', None), '/foo')
+ self.assertEqual(req.get('QUERY_STRING', None), 'bar=baz')
+ self.assertEqual(req.get('REQUEST_METHOD', None), 'GET')
+ self.assertEqual(req.get('SCRIPT_NAME', None), '')
+ self.assertEqual(req.get('SERVER_NAME', None), 'www.example.com')
+ self.assertEqual(req.get('SERVER_PORT', None), '80')
+ req = environ_from_url('https://www.example.com/foo?bar=baz')
+ self.assertEqual(req.get('HTTP_HOST', None), 'www.example.com:443')
+ self.assertEqual(req.get('PATH_INFO', None), '/foo')
+ self.assertEqual(req.get('QUERY_STRING', None), 'bar=baz')
+ self.assertEqual(req.get('REQUEST_METHOD', None), 'GET')
+ self.assertEqual(req.get('SCRIPT_NAME', None), '')
+ self.assertEqual(req.get('SERVER_NAME', None), 'www.example.com')
+ self.assertEqual(req.get('SERVER_PORT', None), '443')
+ environ_add_POST(req, None)
+ self.assert_('CONTENT_TYPE' not in req)
+ self.assert_('CONTENT_LENGTH' not in req)
+ environ_add_POST(req, {'hello':'world'})
+ self.assert_(req.get('HTTP_HOST', None), 'www.example.com:443')
+ self.assertEqual(req.get('PATH_INFO', None), '/foo')
+ self.assertEqual(req.get('QUERY_STRING', None), 'bar=baz')
+ self.assertEqual(req.get('REQUEST_METHOD', None), 'POST')
+ self.assertEqual(req.get('SCRIPT_NAME', None), '')
+ self.assertEqual(req.get('SERVER_NAME', None), 'www.example.com')
+ self.assertEqual(req.get('SERVER_PORT', None), '443')
+ self.assertEqual(req.get('CONTENT_LENGTH', None),'11')
+ self.assertEqual(req.get('CONTENT_TYPE', None),
+ 'application/x-www-form-urlencoded')
+ self.assertEqual(req['wsgi.input'].read(), 'hello=world')
+
+
+ def test_post_does_not_reparse(self):
+ # test that there's no repetitive parsing is happening on every
+ # req.POST access
+ from webob import Request
+ req = Request.blank('/',
+ content_type='multipart/form-data; boundary=boundary',
+ POST=_cgi_escaping_body
+ )
+ f0 = req.body_file_raw
+ post1 = req.str_POST
+ f1 = req.body_file_raw
+ self.assert_(f1 is not f0)
+ post2 = req.str_POST
+ f2 = req.body_file_raw
+ self.assert_(post1 is post2)
+ self.assert_(f1 is f2)
+
+
+ def test_middleware_body(self):
+ from webob import Request
+ def app(env, sr):
+ sr('200 OK', [])
+ return [env['wsgi.input'].read()]
+
+ def mw(env, sr):
+ req = Request(env)
+ data = req.body_file.read()
+ resp = req.get_response(app)
+ resp.headers['x-data'] = data
+ return resp(env, sr)
+
+ req = Request.blank('/', method='PUT', body='abc')
+ resp = req.get_response(mw)
+ self.assertEqual(resp.body, 'abc')
+ self.assertEqual(resp.headers['x-data'], 'abc')
+
+ def test_body_file_noseek(self):
+ from webob import Request
+ req = Request.blank('/', method='PUT', body='abc')
+ lst = [req.body_file.read(1) for i in range(3)]
+ self.assertEqual(lst, ['a','b','c'])
+
+ def test_cgi_escaping_fix(self):
+ from webob import Request
+ req = Request.blank('/',
+ content_type='multipart/form-data; boundary=boundary',
+ POST=_cgi_escaping_body
+ )
+ self.assertEqual(req.POST.keys(), ['%20%22"'])
+ req.body_file.read()
+ self.assertEqual(req.POST.keys(), ['%20%22"'])
+
+ def test_content_type_none(self):
+ from webob import Request
+ r = Request.blank('/', content_type='text/html')
+ self.assertEqual(r.content_type, 'text/html')
+ r.content_type = None
+
+ def test_charset_in_content_type(self):
+ from webob import Request
+ r = Request({'CONTENT_TYPE':'text/html;charset=ascii'})
+ r.charset = 'shift-jis'
+ self.assertEqual(r.charset, 'shift-jis')
+
+ def test_body_file_seekable(self):
+ from cStringIO import StringIO
+ from webob import Request
+ r = Request.blank('/')
+ r.body_file = StringIO('body')
+ self.assertEqual(r.body_file_seekable.read(), 'body')
+
+ def test_request_init(self):
+ # port from doctest (docs/reference.txt)
+ from webob import Request
+ req = Request.blank('/article?id=1')
+ self.assertEqual(req.environ['HTTP_HOST'], 'localhost:80')
+ self.assertEqual(req.environ['PATH_INFO'], '/article')
+ self.assertEqual(req.environ['QUERY_STRING'], 'id=1')
+ self.assertEqual(req.environ['REQUEST_METHOD'], 'GET')
+ self.assertEqual(req.environ['SCRIPT_NAME'], '')
+ self.assertEqual(req.environ['SERVER_NAME'], 'localhost')
+ self.assertEqual(req.environ['SERVER_PORT'], '80')
+ self.assertEqual(req.environ['SERVER_PROTOCOL'], 'HTTP/1.0')
+ self.assert_(hasattr(req.environ['wsgi.errors'], 'write') and
+ hasattr(req.environ['wsgi.errors'], 'flush'))
+ self.assert_(hasattr(req.environ['wsgi.input'], 'next'))
+ self.assertEqual(req.environ['wsgi.multiprocess'], False)
+ self.assertEqual(req.environ['wsgi.multithread'], False)
+ self.assertEqual(req.environ['wsgi.run_once'], False)
+ self.assertEqual(req.environ['wsgi.url_scheme'], 'http')
+ self.assertEqual(req.environ['wsgi.version'], (1, 0))
+
+ # Test body
+ self.assert_(hasattr(req.body_file, 'read'))
+ self.assertEqual(req.body, '')
+ req.body = 'test'
+ self.assert_(hasattr(req.body_file, 'read'))
+ self.assertEqual(req.body, 'test')
+
+ # Test method & URL
+ self.assertEqual(req.method, 'GET')
+ self.assertEqual(req.scheme, 'http')
+ self.assertEqual(req.script_name, '') # The base of the URL
+ req.script_name = '/blog' # make it more interesting
+ self.assertEqual(req.path_info, '/article')
+ # Content-Type of the request body
+ self.assertEqual(req.content_type, '')
+ # The auth'ed user (there is none set)
+ self.assert_(req.remote_user is None)
+ self.assert_(req.remote_addr is None)
+ self.assertEqual(req.host, 'localhost:80')
+ self.assertEqual(req.host_url, 'http://localhost')
+ self.assertEqual(req.application_url, 'http://localhost/blog')
+ self.assertEqual(req.path_url, 'http://localhost/blog/article')
+ self.assertEqual(req.url, 'http://localhost/blog/article?id=1')
+ self.assertEqual(req.path, '/blog/article')
+ self.assertEqual(req.path_qs, '/blog/article?id=1')
+ self.assertEqual(req.query_string, 'id=1')
+ self.assertEqual(req.relative_url('archive'),
+ 'http://localhost/blog/archive')
+
+ # Doesn't change request
+ self.assertEqual(req.path_info_peek(), 'article')
+ # Does change request!
+ self.assertEqual(req.path_info_pop(), 'article')
+ self.assertEqual(req.script_name, '/blog/article')
+ self.assertEqual(req.path_info, '')
+
+ # Headers
+ req.headers['Content-Type'] = 'application/x-www-urlencoded'
+ self.assertEqual(sorted(req.headers.items()),
+ [('Content-Length', '4'),
+ ('Content-Type', 'application/x-www-urlencoded'),
+ ('Host', 'localhost:80')])
+ self.assertEqual(req.environ['CONTENT_TYPE'],
+ 'application/x-www-urlencoded')
+
+ def test_request_query_and_POST_vars(self):
+ # port from doctest (docs/reference.txt)
+
+ # Query & POST variables
+ from webob import Request
+ from webob.multidict import MultiDict
+ from webob.multidict import NestedMultiDict
+ from webob.multidict import NoVars
+ from webob.multidict import TrackableMultiDict
+ req = Request.blank('/test?check=a&check=b&name=Bob')
+ GET = TrackableMultiDict([('check', 'a'),
+ ('check', 'b'),
+ ('name', 'Bob')])
+ self.assertEqual(req.str_GET, GET)
+ self.assertEqual(req.str_GET['check'], 'b')
+ self.assertEqual(req.str_GET.getall('check'), ['a', 'b'])
+ self.assertEqual(req.str_GET.items(),
+ [('check', 'a'), ('check', 'b'), ('name', 'Bob')])
+
+ self.assert_(isinstance(req.str_POST, NoVars))
+ # NoVars can be read like a dict, but not written
+ self.assertEqual(req.str_POST.items(), [])
+ req.method = 'POST'
+ req.body = 'name=Joe&email=joe@example.com'
+ self.assertEqual(req.str_POST,
+ MultiDict([('name', 'Joe'),
+ ('email', 'joe@example.com')]))
+ self.assertEqual(req.str_POST['name'], 'Joe')
+
+ self.assert_(isinstance(req.str_params, NestedMultiDict))
+ self.assertEqual(req.str_params.items(),
+ [('check', 'a'),
+ ('check', 'b'),
+ ('name', 'Bob'),
+ ('name', 'Joe'),
+ ('email', 'joe@example.com')])
+ self.assertEqual(req.str_params['name'], 'Bob')
+ self.assertEqual(req.str_params.getall('name'), ['Bob', 'Joe'])
+
+ def test_request_put(self):
+ from datetime import datetime
+ from webob import Request
+ from webob import Response
+ from webob import UTC
+ from webob.acceptparse import MIMEAccept
+ from webob.byterange import Range
+ from webob.etag import ETagMatcher
+ from webob.etag import _NoIfRange
+ from webob.multidict import MultiDict
+ from webob.multidict import TrackableMultiDict
+ from webob.multidict import UnicodeMultiDict
+ req = Request.blank('/test?check=a&check=b&name=Bob')
+ req.method = 'PUT'
+ req.body = 'var1=value1&var2=value2&rep=1&rep=2'
+ req.environ['CONTENT_LENGTH'] = str(len(req.body))
+ req.environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
+ GET = TrackableMultiDict([('check', 'a'),
+ ('check', 'b'),
+ ('name', 'Bob')])
+ self.assertEqual(req.str_GET, GET)
+ self.assertEqual(req.str_POST, MultiDict(
+ [('var1', 'value1'),
+ ('var2', 'value2'),
+ ('rep', '1'),
+ ('rep', '2')]))
+
+ # Unicode
+ req.charset = 'utf8'
+ self.assert_(isinstance(req.GET, UnicodeMultiDict))
+ self.assertEqual(req.GET.items(),
+ [('check', u'a'), ('check', u'b'), ('name', u'Bob')])
+
+ # Cookies
+ req.headers['Cookie'] = 'test=value'
+ self.assert_(isinstance(req.cookies, UnicodeMultiDict))
+ self.assertEqual(req.cookies.items(), [('test', u'value')])
+ req.charset = None
+ self.assertEqual(req.str_cookies, {'test': 'value'})
+
+ # Accept-* headers
+ self.assert_('text/html' in req.accept)
+ req.accept = 'text/html;q=0.5, application/xhtml+xml;q=1'
+ self.assert_(isinstance(req.accept, MIMEAccept))
+ self.assert_('text/html' in req.accept)
+
+ self.assertEqual(req.accept.first_match(['text/html',
+ 'application/xhtml+xml']), 'text/html')
+ self.assertEqual(req.accept.best_match(['text/html',
+ 'application/xhtml+xml']),
+ 'application/xhtml+xml')
+ self.assertEqual(req.accept.best_matches(),
+ ['application/xhtml+xml', 'text/html'])
+
+ req.accept_language = 'es, pt-BR'
+ self.assertEqual(req.accept_language.best_matches('en-US'),
+ ['es', 'pt-BR', 'en-US'])
+ self.assertEqual(req.accept_language.best_matches('es'), ['es'])
+
+ # Conditional Requests
+ server_token = 'opaque-token'
+ # shouldn't return 304
+ self.assert_(not server_token in req.if_none_match)
+ req.if_none_match = server_token
+ self.assert_(isinstance(req.if_none_match, ETagMatcher))
+ # You *should* return 304
+ self.assert_(server_token in req.if_none_match)
+
+ req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
+ self.assertEqual(req.headers['If-Modified-Since'],
+ 'Sun, 01 Jan 2006 12:00:00 GMT')
+ server_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
+ self.assert_(req.if_modified_since)
+ self.assert_(req.if_modified_since >= server_modified)
+
+ self.assert_(isinstance(req.if_range, _NoIfRange))
+ self.assert_(req.if_range.match(etag='some-etag',
+ last_modified=datetime(2005, 1, 1, 12, 0)))
+ req.if_range = 'opaque-etag'
+ self.assert_(not req.if_range.match(etag='other-etag'))
+ self.assert_(req.if_range.match(etag='opaque-etag'))
+
+ res = Response(etag='opaque-etag')
+ self.assert_(req.if_range.match_response(res))
+
+ req.range = 'bytes=0-100'
+ self.assert_(isinstance(req.range, Range))
+ self.assertEqual(req.range.ranges, [(0, 101)])
+ cr = req.range.content_range(length=1000)
+ self.assertEqual((cr.start, cr.stop, cr.length), (0, 101, 1000))
+
+ self.assert_(server_token in req.if_match)
+ # No If-Match means everything is ok
+ req.if_match = server_token
+ self.assert_(server_token in req.if_match)
+ # Still OK
+ req.if_match = 'other-token'
+ # Not OK, should return 412 Precondition Failed:
+ self.assert_(not server_token in req.if_match)
+
+ def test_call_WSGI_app(self):
+ from webob import Request
+ req = Request.blank('/')
+ def wsgi_app(environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/plain')])
+ return ['Hi!']
+ self.assertEqual(req.call_application(wsgi_app),
+ ('200 OK', [('Content-type', 'text/plain')], ['Hi!']))
+
+ res = req.get_response(wsgi_app)
+ from webob.response import Response
+ self.assert_(isinstance(res, Response))
+ self.assertEqual(res.status, '200 OK')
+ from webob.headers import ResponseHeaders
+ self.assert_(isinstance(res.headers, ResponseHeaders))
+ self.assertEqual(res.headers.items(), [('Content-type', 'text/plain')])
+ self.assertEqual(res.body, 'Hi!')
+
+ def equal_req(self, req):
+ from cStringIO import StringIO
+ from webob import Request
+ input = StringIO(str(req))
+ req2 = Request.from_file(input)
+ self.assertEqual(req.url, req2.url)
+ headers1 = dict(req.headers)
+ headers2 = dict(req2.headers)
+ self.assertEqual(int(headers1.get('Content-Length', '0')),
+ int(headers2.get('Content-Length', '0')))
+ if 'Content-Length' in headers1:
+ del headers1['Content-Length']
+ if 'Content-Length' in headers2:
+ del headers2['Content-Length']
+ self.assertEqual(headers1, headers2)
+ self.assertEqual(req.body, req2.body)
+
def simpleapp(environ, start_response):
+ from webob import Request
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
@@ -18,15 +2275,21 @@ def simpleapp(environ, start_response):
'Hello world!\n',
'The get is %r' % request.str_GET,
' and Val is %s\n' % request.str_GET.get('name'),
- 'The languages are: %s\n' % request.accept_language.best_matches('en-US'),
- 'The accepttypes is: %s\n' % request.accept.best_match(['application/xml', 'text/html']),
+ 'The languages are: %s\n' %
+ request.accept_language.best_matches('en-US'),
+ 'The accepttypes is: %s\n' %
+ request.accept.best_match(['application/xml', 'text/html']),
'post is %r\n' % request.str_POST,
'params is %r\n' % request.str_params,
'cookies is %r\n' % request.str_cookies,
'body: %r\n' % request.body,
'method: %s\n' % request.method,
'remote_user: %r\n' % request.environ['REMOTE_USER'],
- 'host_url: %r; application_url: %r; path_url: %r; url: %r\n' % (request.host_url, request.application_url, request.path_url, request.url),
+ 'host_url: %r; application_url: %r; path_url: %r; url: %r\n' %
+ (request.host_url,
+ request.application_url,
+ request.path_url,
+ request.url),
'urlvars: %r\n' % request.urlvars,
'urlargs: %r\n' % (request.urlargs, ),
'is_xhr: %r\n' % request.is_xhr,
@@ -35,537 +2298,13 @@ def simpleapp(environ, start_response):
'if_none_match: %r\n' % request.if_none_match,
]
-def test_gets():
- app = TestApp(simpleapp)
- res = app.get('/')
- assert 'Hello' in res
- assert "get is GET([])" in res
- assert "post is <NoVars: Not a form request>" in res
-
- res = app.get('/?name=george')
- res.mustcontain("get is GET([('name', 'george')])")
- res.mustcontain("Val is george")
-
-def test_language_parsing():
- app = TestApp(simpleapp)
- res = app.get('/')
- assert "The languages are: ['en-US']" in res
-
- res = app.get('/', headers={'Accept-Language':'da, en-gb;q=0.8, en;q=0.7'})
- assert "languages are: ['da', 'en-gb', 'en-US']" in res
-
- res = app.get('/', headers={'Accept-Language':'en-gb;q=0.8, da, en;q=0.7'})
- assert "languages are: ['da', 'en-gb', 'en-US']" in res
-
-def test_mime_parsing():
- app = TestApp(simpleapp)
- res = app.get('/', headers={'Accept':'text/html'})
- assert "accepttypes is: text/html" in res
-
- res = app.get('/', headers={'Accept':'application/xml'})
- assert "accepttypes is: application/xml" in res
-
- res = app.get('/', headers={'Accept':'application/xml,*/*'})
- assert "accepttypes is: application/xml" in res, res
-
-
-def test_accept_best_match():
- assert not Request.blank('/').accept
- assert not Request.blank('/', headers={'Accept': ''}).accept
- req = Request.blank('/', headers={'Accept':'text/plain'})
- ok_(req.accept)
- assert_raises(ValueError, req.accept.best_match, ['*/*'])
- req = Request.blank('/', accept=['*/*','text/*'])
- eq_(req.accept.best_match(['application/x-foo', 'text/plain']), 'text/plain')
- eq_(req.accept.best_match(['text/plain', 'application/x-foo']), 'text/plain')
- req = Request.blank('/', accept=['text/plain', 'message/*'])
- eq_(req.accept.best_match(['message/x-foo', 'text/plain']), 'text/plain')
- eq_(req.accept.best_match(['text/plain', 'message/x-foo']), 'text/plain')
-
-def test_from_mimeparse():
- # http://mimeparse.googlecode.com/svn/trunk/mimeparse.py
- supported = ['application/xbel+xml', 'application/xml']
- tests = [('application/xbel+xml', 'application/xbel+xml'),
- ('application/xbel+xml; q=1', 'application/xbel+xml'),
- ('application/xml; q=1', 'application/xml'),
- ('application/*; q=1', 'application/xbel+xml'),
- ('*/*', 'application/xbel+xml')]
-
- for accept, get in tests:
- req = Request.blank('/', headers={'Accept':accept})
- assert req.accept.best_match(supported) == get, (
- '%r generated %r instead of %r for %r' % (accept, req.accept.best_match(supported), get, supported))
-
- supported = ['application/xbel+xml', 'text/xml']
- tests = [('text/*;q=0.5,*/*; q=0.1', 'text/xml'),
- ('text/html,application/atom+xml; q=0.9', None)]
-
- for accept, get in tests:
- req = Request.blank('/', headers={'Accept':accept})
- assert req.accept.best_match(supported) == get, (
- 'Got %r instead of %r for %r' % (req.accept.best_match(supported), get, supported))
-
- supported = ['application/json', 'text/html']
- tests = [('application/json, text/javascript, */*', 'application/json'),
- ('application/json, text/html;q=0.9', 'application/json')]
-
- for accept, get in tests:
- req = Request.blank('/', headers={'Accept':accept})
- assert req.accept.best_match(supported) == get, (
- '%r generated %r instead of %r for %r' % (accept, req.accept.best_match(supported), get, supported))
-
- offered = ['image/png', 'application/xml']
- tests = [
- ('image/png', 'image/png'),
- ('image/*', 'image/png'),
- ('image/*, application/xml', 'application/xml'),
- ]
-
- for accept, get in tests:
- req = Request.blank('/', accept=accept)
- eq_(req.accept.best_match(offered), get)
-
-def test_headers():
- app = TestApp(simpleapp)
- headers = {
- 'If-Modified-Since': 'Sat, 29 Oct 1994 19:43:31 GMT',
- 'Cookie': 'var1=value1',
- 'User-Agent': 'Mozilla 4.0 (compatible; MSIE)',
- 'If-None-Match': '"etag001", "etag002"',
- 'X-Requested-With': 'XMLHttpRequest',
- }
- res = app.get('/?foo=bar&baz', headers=headers)
- res.mustcontain(
- 'if_modified_since: datetime.datetime(1994, 10, 29, 19, 43, 31, tzinfo=UTC)',
- "user_agent: 'Mozilla",
- 'is_xhr: True',
- "cookies is {'var1': 'value1'}",
- "params is NestedMultiDict([('foo', 'bar'), ('baz', '')])",
- "if_none_match: <ETag etag001 or etag002>",
- )
-def test_bad_cookie():
- req = Request.blank('/')
- req.headers['Cookie'] = '070-it-:><?0'
- assert req.cookies == {}
- req.headers['Cookie'] = 'foo=bar'
- assert req.cookies == {'foo': 'bar'}
- req.headers['Cookie'] = '...'
- assert req.cookies == {}
- req.headers['Cookie'] = '=foo'
- assert req.cookies == {}
- req.headers['Cookie'] = 'dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cdc762809248d4beed; a=42'
- eq_(req.cookies, {
- 'CP': u'null*',
- 'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
- 'a': u'42',
- 'dismiss-top': u'6'
- })
- req.headers['Cookie'] = 'fo234{=bar blub=Blah'
- assert req.cookies == {'blub': 'Blah'}
-
-def test_cookie_quoting():
- req = Request.blank('/')
- req.headers['Cookie'] = 'foo="?foo"; Path=/'
- assert req.cookies == {'foo': '?foo'}
-
-def test_path_quoting():
- path = '/:@&+$,/bar'
- req = Request.blank(path)
- assert req.path == path
- assert req.url.endswith(path)
-
-def test_params():
- req = Request.blank('/?a=1&b=2')
- req.method = 'POST'
- req.body = 'b=3'
- assert req.params.items() == [('a', '1'), ('b', '2'), ('b', '3')]
- new_params = req.params.copy()
- assert new_params.items() == [('a', '1'), ('b', '2'), ('b', '3')]
- new_params['b'] = '4'
- assert new_params.items() == [('a', '1'), ('b', '4')]
- # The key name is \u1000:
- req = Request.blank('/?%E1%80%80=x', decode_param_names=True, charset='UTF-8')
- assert req.decode_param_names
- assert u'\u1000' in req.GET.keys()
- assert req.GET[u'\u1000'] == 'x'
-
-class UnseekableInput(object):
- def __init__(self, data):
- self.data = data
- self.pos = 0
- def read(self, size=-1):
- if size == -1:
- t = self.data[self.pos:]
- self.pos = len(self.data)
- return t
- else:
- assert self.pos + size <= len(self.data), (
- "Attempt to read past end (length=%s, position=%s, reading %s bytes)"
- % (len(self.data), self.pos, size))
- t = self.data[self.pos:self.pos+size]
- self.pos += size
- return t
-def test_copy_body():
- req = Request.blank('/', method='POST', body='some text', request_body_tempfile_limit=1)
- old_body_file = req.body_file_raw
- req.copy_body()
- assert req.body_file_raw is not old_body_file
- req = Request.blank('/', method='POST', body_file=UnseekableInput('0123456789'), content_length=10)
- assert not hasattr(req.body_file_raw, 'seek')
- old_body_file = req.body_file_raw
- req.make_body_seekable()
- assert req.body_file_raw is not old_body_file
- assert req.body == '0123456789'
- old_body_file = req.body_file
- req.make_body_seekable()
- assert req.body_file_raw is old_body_file
- assert req.body_file is old_body_file
+_cgi_escaping_body = '''--boundary
+Content-Disposition: form-data; name="%20%22""
-class UnseekableInputWithSeek(UnseekableInput):
- def seek(self, pos, rel=0):
- raise IOError("Invalid seek!")
-def test_broken_seek():
- # copy() should work even when the input has a broken seek method
- req = Request.blank('/', method='POST', body_file=UnseekableInputWithSeek('0123456789'), content_length=10)
- assert hasattr(req.body_file_raw, 'seek')
- assert_raises(IOError, req.body_file_raw.seek, 0)
- old_body_file = req.body_file
- req2 = req.copy()
- assert req2.body_file_raw is req2.body_file is not old_body_file
- assert req2.body == '0123456789'
-
-def test_set_body():
- req = BaseRequest.blank('/', body='foo')
- assert req.is_body_seekable
- eq_(req.body, 'foo')
- eq_(req.content_length, 3)
- del req.body
- eq_(req.body, '')
- eq_(req.content_length, 0)
-
-
-
-def test_broken_clen_header():
- # if the UA sends "content_length: ..' header (the name is wrong)
- # it should not break the req.headers.items()
- req = Request.blank('/')
- req.environ['HTTP_CONTENT_LENGTH'] = '0'
- req.headers.items()
-
-
-def test_nonstr_keys():
- # non-string env keys shouldn't break req.headers
- req = Request.blank('/')
- req.environ[1] = 1
- req.headers.items()
-
-
-def test_authorization():
- req = Request.blank('/')
- req.authorization = 'Digest uri="/?a=b"'
- assert req.authorization == ('Digest', {'uri': '/?a=b'})
-
-def test_authorization2():
- from webob.descriptors import parse_auth_params
- for s, d in [
- ('x=y', {'x': 'y'}),
- ('x="y"', {'x': 'y'}),
- ('x=y,z=z', {'x': 'y', 'z': 'z'}),
- ('x=y, z=z', {'x': 'y', 'z': 'z'}),
- ('x="y",z=z', {'x': 'y', 'z': 'z'}),
- ('x="y", z=z', {'x': 'y', 'z': 'z'}),
- ('x="y,x", z=z', {'x': 'y,x', 'z': 'z'}),
- ]:
- eq_(parse_auth_params(s), d)
-
-
-def test_from_file():
- req = Request.blank('http://example.com:8000/test.html?params')
- equal_req(req)
-
- req = Request.blank('http://example.com/test2')
- req.method = 'POST'
- req.body = 'test=example'
- equal_req(req)
-
-def equal_req(req):
- input = StringIO(str(req))
- req2 = Request.from_file(input)
- eq_(req.url, req2.url)
- headers1 = dict(req.headers)
- headers2 = dict(req2.headers)
- eq_(int(headers1.get('Content-Length', '0')),
- int(headers2.get('Content-Length', '0')))
- if 'Content-Length' in headers1:
- del headers1['Content-Length']
- if 'Content-Length' in headers2:
- del headers2['Content-Length']
- eq_(headers1, headers2)
- eq_(req.body, req2.body)
-
-def test_req_kw_none_val():
- assert 'content-length' not in Request({}, content_length=None).headers
- assert 'content-type' not in Request({}, content_type=None).headers
-
-def test_env_keys():
- req = Request.blank('/')
- # SCRIPT_NAME can be missing
- del req.environ['SCRIPT_NAME']
- eq_(req.script_name, '')
- eq_(req.uscript_name, u'')
-
-def test_repr_nodefault():
- nd = NoDefault
- eq_(repr(nd), '(No Default)')
-
-def test_request_noenviron_param():
- """Environ is a a mandatory not null param in Request"""
- assert_raises(TypeError, Request, environ=None)
-
-def test_environ_getter():
- """
- Parameter environ_getter in Request is no longer valid and should raise
- an error in case it's used
- """
- class env(object):
- def __init__(self, env):
- self.env = env
- def env_getter(self):
- return self.env
- assert_raises(ValueError, Request, environ_getter=env({'a':1}).env_getter)
-
-def test_unicode_errors():
- """
- Passing unicode_errors != NoDefault should assign value to
- dictionary['unicode_errors'], else not
- """
- r = Request({'a':1}, unicode_errors='strict')
- ok_('unicode_errors' in r.__dict__)
- r = Request({'a':1}, unicode_errors=NoDefault)
- ok_('unicode_errors' not in r.__dict__)
-
-def test_charset_deprecation():
- """
- Any class that inherits from BaseRequest cannot define a default_charset
- attribute.
- Any class that inherits from BaseRequest cannot define a charset attr
- that is instance of str
- """
- class NewRequest(BaseRequest):
- default_charset = 'utf-8'
- def __init__(self, environ, **kw):
- super(NewRequest, self).__init__(environ, **kw)
- assert_raises(DeprecationWarning, NewRequest, {'a':1})
- class NewRequest(BaseRequest):
- charset = 'utf-8'
- def __init__(self, environ, **kw):
- super(NewRequest, self).__init__(environ, **kw)
- assert_raises(DeprecationWarning, NewRequest, {'a':1})
- class NewRequest(AdhocAttrMixin, BaseRequest):
- default_charset = 'utf-8'
- def __init__(self, environ, **kw):
- super(NewRequest, self).__init__(environ, **kw)
- assert_raises(DeprecationWarning, NewRequest, {'a':1})
- class NewRequest(AdhocAttrMixin, BaseRequest):
- charset = 'utf-8'
- def __init__(self, environ, **kw):
- super(NewRequest, self).__init__(environ, **kw)
- assert_raises(DeprecationWarning, NewRequest, {'a':1})
-
-def test_unexpected_kw():
- """
- Passed an attr in kw that does not exist in the class, should raise an
- error
- Passed an attr in kw that does exist in the class, should be ok
- """
- assert_raises(TypeError, Request, {'a':1}, **{'this_does_not_exist':1})
- r = Request({'a':1}, **{'charset':'utf-8', 'server_name':'127.0.0.1'})
- eq_(getattr(r, 'charset', None), 'utf-8')
- eq_(getattr(r, 'server_name', None), '127.0.0.1')
-
-def test_body_file_setter():
- """"
- If body_file is passed and it's instance of str, we define
- environ['wsgi.input'] and content_length. Plus, while deleting the
- attribute, we should get '' and 0 respectively
- """
- r = Request({'a':1}, **{'body_file':'hello world'})
- eq_(r.environ['wsgi.input'].getvalue(), 'hello world')
- eq_(int(r.environ['CONTENT_LENGTH']), len('hello world'))
- del r.body_file
- eq_(r.environ['wsgi.input'].getvalue(), '')
- eq_(int(r.environ['CONTENT_LENGTH']), 0)
-
-def test_conttype_set_del():
- """
- Deleting content_type attr from a request should update the environ dict
- Assigning content_type should replace first option of the environ dict
- """
- r = Request({'a':1}, **{'content_type':'text/html'})
- ok_('CONTENT_TYPE' in r.environ)
- ok_(hasattr(r, 'content_type'))
- del r.content_type
- ok_('CONTENT_TYPE' not in r.environ)
- a = Request({'a':1},**{'content_type':'charset=utf-8;application/atom+xml;type=entry'})
- ok_(a.environ['CONTENT_TYPE']=='charset=utf-8;application/atom+xml;type=entry')
- a.content_type = 'charset=utf-8'
- ok_(a.environ['CONTENT_TYPE']=='charset=utf-8;application/atom+xml;type=entry')
-
-def test_headers():
- """
- Setting headers in init and later with a property, should update the info
- """
- headers = {'Host': 'www.example.com',
- 'Accept-Language': 'en-us,en;q=0.5',
- 'Accept-Encoding': 'gzip,deflate',
- 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
- 'Keep-Alive': '115',
- 'Connection': 'keep-alive',
- 'Cache-Control': 'max-age=0'}
- r = Request({'a':1}, headers=headers)
- for i in headers.keys():
- ok_(i in r.headers and
- 'HTTP_'+i.upper().replace('-', '_') in r.environ)
- r.headers = {'Server':'Apache'}
- eq_(r.environ.keys(), ['a', 'HTTP_SERVER'])
-
-def test_host_url():
- """
- Request has a read only property host_url that combines several keys to
- create a host_url
- """
- a = Request({'wsgi.url_scheme':'http'}, **{'host':'www.example.com'})
- eq_(a.host_url, 'http://www.example.com')
- a = Request({'wsgi.url_scheme':'http'}, **{'server_name':'localhost',
- 'server_port':5000})
- eq_(a.host_url, 'http://localhost:5000')
- a = Request({'wsgi.url_scheme':'https'}, **{'server_name':'localhost',
- 'server_port':443})
- eq_(a.host_url, 'https://localhost')
-
-def test_path_info_p():
- """
- Peek path_info to see what's coming
- Pop path_info until there's nothing remaining
- """
- a = Request({'a':1}, **{'path_info':'/foo/bar','script_name':''})
- eq_(a.path_info_peek(), 'foo')
- eq_(a.path_info_pop(), 'foo')
- eq_(a.path_info_peek(), 'bar')
- eq_(a.path_info_pop(), 'bar')
- eq_(a.path_info_peek(), None)
- eq_(a.path_info_pop(), None)
-
-def test_urlvars_property():
- """
- Testing urlvars setter/getter/deleter
- """
- a = Request({'wsgiorg.routing_args':((),{'x':'y'}),
- 'paste.urlvars':{'test':'value'}})
- a.urlvars = {'hello':'world'}
- ok_('paste.urlvars' not in a.environ)
- eq_(a.environ['wsgiorg.routing_args'], ((), {'hello':'world'}))
- del a.urlvars
- ok_('wsgiorg.routing_args' not in a.environ)
- a = Request({'paste.urlvars':{'test':'value'}})
- eq_(a.urlvars, {'test':'value'})
- a.urlvars = {'hello':'world'}
- eq_(a.environ['paste.urlvars'], {'hello':'world'})
- del a.urlvars
- ok_('paste.urlvars' not in a.environ)
-
-def test_urlargs_property():
- """
- Testing urlargs setter/getter/deleter
- """
- a = Request({'paste.urlvars':{'test':'value'}})
- eq_(a.urlargs, ())
- a.urlargs = {'hello':'world'}
- eq_(a.environ['wsgiorg.routing_args'], ({'hello':'world'},
- {'test':'value'}))
- a = Request({'a':1})
- a.urlargs = {'hello':'world'}
- eq_(a.environ['wsgiorg.routing_args'], ({'hello':'world'}, {}))
- del a.urlargs
- ok_('wsgiorg.routing_args' not in a.environ)
-
-def test_host_property():
- """
- Testing host setter/getter/deleter
- """
- a = Request({'wsgi.url_scheme':'http'}, **{'server_name':'localhost',
- 'server_port':5000})
- eq_(a.host, "localhost:5000")
- a.host = "localhost:5000"
- ok_('HTTP_HOST' in a.environ)
- del a.host
- ok_('HTTP_HOST' not in a.environ)
-
-def test_body_property():
- """
- Testing body setter/getter/deleter plus making sure body has a seek method
- """
- #a = Request({'a':1}, **{'CONTENT_LENGTH':'?'})
- # I cannot think of a case where somebody would put anything else than a
- # numerical value in CONTENT_LENGTH, Google didn't help either
- #eq_(a.body, '')
- # I need to implement a not seekable stringio like object.
- class DummyIO(object):
- def __init__(self, txt):
- self.txt = txt
- def read(self, n=-1):
- return self.txt[0:n]
- len_strl = BaseRequest.request_body_tempfile_limit/len(string.letters)+1
- r = Request({'a':1}, **{'body_file':DummyIO(string.letters*len_strl)})
- eq_(len(r.body), len(string.letters*len_strl)-1)
- assert_raises(TypeError, setattr, r, 'body', unicode('hello world'))
- r.body = None
- eq_(r.body, '')
- r = Request({'a':1}, **{'body_file':DummyIO(string.letters)})
- assert not hasattr(r.body_file_raw, 'seek')
- r.make_body_seekable()
- assert hasattr(r.body_file_raw, 'seek')
- r = Request({'a':1}, **{'body_file':StringIO(string.letters)})
- assert hasattr(r.body_file_raw, 'seek')
- r.make_body_seekable()
- assert hasattr(r.body_file_raw, 'seek')
-
-def test_repr_invalid():
- """If we have an invalid WSGI environ, the repr should tell us"""
- req = BaseRequest({'CONTENT_LENGTH':'0', 'body':''})
- ok_(repr(req).endswith('(invalid WSGI environ)>'))
-
-def test_from_file():
- """If we pass a file with garbage to from_file method it should raise an
- error plus missing bits in from_file method
- """
- assert_raises(ValueError, BaseRequest.from_file, StringIO('hello world'))
- val_file = StringIO(
- "GET /webob/ HTTP/1.1\n"
- "Host: pythonpaste.org\n"
- "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13)"
- "Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13\n"
- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;"
- "q=0.8\n"
- "Accept-Language: en-us,en;q=0.5\n"
- "Accept-Encoding: gzip,deflate\n"
- "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n"
- # duplicate on porpouse
- "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n"
- "Keep-Alive: 115\n"
- "Connection: keep-alive\n"
- )
- req = BaseRequest.from_file(val_file)
- assert isinstance(req, BaseRequest)
- assert_false(repr(req).endswith('(invalid WSGI environ)>'))
- val_file = StringIO(
- "GET /webob/ HTTP/1.1\n"
- "Host pythonpaste.org\n"
- )
- assert_raises(ValueError, BaseRequest.from_file, val_file)
+--boundary--'''
def _norm_req(s):
return '\r\n'.join(s.strip().replace('\r','').split('\n'))
@@ -590,6 +2329,7 @@ these are the contents of the file 'bar.txt'
------------------------------deb95b63e42a--
"""
+
_test_req2 = """
POST / HTTP/1.0
Content-Length: 0
@@ -599,149 +2339,128 @@ Content-Length: 0
_test_req = _norm_req(_test_req)
_test_req2 = _norm_req(_test_req2) + '\r\n'
-def test_from_string():
- """A valid request without a Content-Length header should still read the full body.
- Also test parity between as_string and from_string / from_file.
- """
- req = BaseRequest.from_string(_test_req)
- assert isinstance(req, BaseRequest)
- assert_false(repr(req).endswith('(invalid WSGI environ)>'))
- assert_false('\n' in req.http_version or '\r' in req.http_version)
- assert ',' not in req.host
- assert req.content_length is not None
- assert req.content_length == 337
- assert 'foo' in req.body
- bar_contents = "these are the contents of the file 'bar.txt'\r\n"
- assert bar_contents in req.body
- assert req.params['foo'] == 'foo'
- bar = req.params['bar']
- assert isinstance(bar, cgi.FieldStorage)
- assert bar.type == 'application/octet-stream'
- bar.file.seek(0)
- assert bar.file.read() == bar_contents
- # out should equal contents, except for the Content-Length header,
- # so insert that.
- _test_req_copy = _test_req.replace('Content-Type', 'Content-Length: 337\r\nContent-Type')
- assert str(req) == _test_req_copy
-
- req2 = BaseRequest.from_string(_test_req2)
- assert 'host' not in req2.headers
- eq_(str(req2), _test_req2.rstrip())
- assert_raises(ValueError, BaseRequest.from_string, _test_req2+'xx')
-
-
-def test_blank():
- """BaseRequest.blank class method"""
- assert_raises(ValueError, BaseRequest.blank,
- 'www.example.com/foo?hello=world', None,
- 'www.example.com/foo?hello=world')
- assert_raises(ValueError, BaseRequest.blank,
- 'gopher.example.com/foo?hello=world', None,
- 'gopher://gopher.example.com')
- req = BaseRequest.blank('www.example.com/foo?hello=world', None,
- 'http://www.example.com')
- ok_(req.environ.get('HTTP_HOST', None)== 'www.example.com:80' and
- req.environ.get('PATH_INFO', None)== 'www.example.com/foo' and
- req.environ.get('QUERY_STRING', None)== 'hello=world' and
- req.environ.get('REQUEST_METHOD', None)== 'GET')
- req = BaseRequest.blank('www.example.com/secure?hello=world', None,
- 'https://www.example.com/secure')
- ok_(req.environ.get('HTTP_HOST', None)== 'www.example.com:443' and
- req.environ.get('PATH_INFO', None)== 'www.example.com/secure' and
- req.environ.get('QUERY_STRING', None)== 'hello=world' and
- req.environ.get('REQUEST_METHOD', None)== 'GET' and
- req.environ.get('SCRIPT_NAME', None)== '/secure' and
- req.environ.get('SERVER_NAME', None)== 'www.example.com' and
- req.environ.get('SERVER_PORT', None)== '443')
-
-def test_environ_from_url():
- """Generating an environ just from an url plus testing environ_add_POST"""
- assert_raises(TypeError, environ_from_url,
- 'http://www.example.com/foo?bar=baz#qux')
- assert_raises(TypeError, environ_from_url,
- 'gopher://gopher.example.com')
- req = environ_from_url('http://www.example.com/foo?bar=baz')
- ok_(req.get('HTTP_HOST', None)== 'www.example.com:80' and
- req.get('PATH_INFO', None)== '/foo' and
- req.get('QUERY_STRING', None)== 'bar=baz' and
- req.get('REQUEST_METHOD', None)== 'GET' and
- req.get('SCRIPT_NAME', None)== '' and
- req.get('SERVER_NAME', None)== 'www.example.com' and
- req.get('SERVER_PORT', None)== '80')
- req = environ_from_url('https://www.example.com/foo?bar=baz')
- ok_(req.get('HTTP_HOST', None)== 'www.example.com:443' and
- req.get('PATH_INFO', None)== '/foo' and
- req.get('QUERY_STRING', None)== 'bar=baz' and
- req.get('REQUEST_METHOD', None)== 'GET' and
- req.get('SCRIPT_NAME', None)== '' and
- req.get('SERVER_NAME', None)== 'www.example.com' and
- req.get('SERVER_PORT', None)== '443')
- environ_add_POST(req, None)
- assert_false('CONTENT_TYPE' in req and 'CONTENT_LENGTH' in req)
- environ_add_POST(req, {'hello':'world'})
- ok_(req.get('HTTP_HOST', None)== 'www.example.com:443' and
- req.get('PATH_INFO', None)== '/foo' and
- req.get('QUERY_STRING', None)== 'bar=baz' and
- req.get('REQUEST_METHOD', None)== 'POST' and
- req.get('SCRIPT_NAME', None)== '' and
- req.get('SERVER_NAME', None)== 'www.example.com' and
- req.get('SERVER_PORT', None)== '443' and
- req.get('CONTENT_LENGTH', None)=='11' and
- req.get('CONTENT_TYPE', None)=='application/x-www-form-urlencoded')
- eq_(req['wsgi.input'].read(), 'hello=world')
-
-
-def test_post_does_not_reparse():
- # test that there's no repetitive parsing is happening on every req.POST access
- req = Request.blank('/',
- content_type='multipart/form-data; boundary=boundary',
- POST=_cgi_escaping_body
- )
- f0 = req.body_file_raw
- post1 = req.str_POST
- f1 = req.body_file_raw
- assert f1 is not f0
- post2 = req.str_POST
- f2 = req.body_file_raw
- assert post1 is post2
- assert f1 is f2
-
-
-def test_middleware_body():
- def app(env, sr):
- sr('200 OK', [])
- return [env['wsgi.input'].read()]
-
- def mw(env, sr):
- req = Request(env)
- data = req.body_file.read()
- resp = req.get_response(app)
- resp.headers['x-data'] = data
- return resp(env, sr)
-
- req = Request.blank('/', method='PUT', body='abc')
- resp = req.get_response(mw)
- eq_(resp.body, 'abc')
- eq_(resp.headers['x-data'], 'abc')
-
-def test_body_file_noseek():
- req = Request.blank('/', method='PUT', body='abc')
- lst = [req.body_file.read(1) for i in range(3)]
- eq_(lst, ['a','b','c'])
-
-def test_cgi_escaping_fix():
- req = Request.blank('/',
- content_type='multipart/form-data; boundary=boundary',
- POST=_cgi_escaping_body
- )
- eq_(req.POST.keys(), ['%20%22"'])
- req.body_file.read()
- eq_(req.POST.keys(), ['%20%22"'])
-
-_cgi_escaping_body = '''--boundary
-Content-Disposition: form-data; name="%20%22""
+class UnseekableInput(object):
+ def __init__(self, data):
+ self.data = data
+ self.pos = 0
+ def read(self, size=-1):
+ if size == -1:
+ t = self.data[self.pos:]
+ self.pos = len(self.data)
+ return t
+ else:
+ assert(self.pos + size <= len(self.data))
+ t = self.data[self.pos:self.pos+size]
+ self.pos += size
+ return t
+class UnseekableInputWithSeek(UnseekableInput):
+ def seek(self, pos, rel=0):
+ raise IOError("Invalid seek!")
---boundary--'''
+class FakeCGIBodyTests(unittest.TestCase):
+
+ def test_encode_multipart_value_type_options(self):
+ from StringIO import StringIO
+ from cgi import FieldStorage
+ from webob.request import BaseRequest, FakeCGIBody
+ from webob.multidict import MultiDict
+ multipart_type = 'multipart/form-data; boundary=foobar'
+ multipart_body = StringIO(
+ '--foobar\r\n'
+ 'Content-Disposition: form-data; name="bananas"; filename="bananas.txt"\r\n'
+ 'Content-type: text/plain; charset="utf-9"\r\n'
+ '\r\n'
+ "these are the contents of the file 'bananas.txt'\r\n"
+ '\r\n'
+ '--foobar--'
+ )
+ environ = BaseRequest.blank('/').environ
+ environ.update(CONTENT_TYPE=multipart_type)
+ environ.update(REQUEST_METHOD='POST')
+ fs = FieldStorage(multipart_body, environ=environ)
+ vars = MultiDict.from_fieldstorage(fs)
+ self.assertEqual(vars['bananas'].__class__, FieldStorage)
+ body = FakeCGIBody(vars, multipart_type)
+ self.assertEqual(body.read(), multipart_body.getvalue())
+
+ def test_encode_multipart_no_boundary(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({}, 'multipart/form-data')
+ self.assertRaises(ValueError, body.read)
+
+ def test_repr(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({'bananas': 'bananas'}, 'multipart/form-data; boundary=foobar')
+ body.read(1)
+ import re
+ self.assertEqual(
+ re.sub(r'\b0x[0-9a-f]+\b', '<whereitsat>', repr(body)),
+ "<FakeCGIBody at <whereitsat> viewing {'bananas': 'ba...nas'} at position 1>",
+ )
+ def test_seek_tell(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({'bananas': 'bananas'}, 'multipart/form-data; boundary=foobar')
+ self.assertEqual(body.tell(), 0)
+ body.seek(1)
+ self.assertEqual(body.tell(), 1)
+ self.assertRaises(IOError, body.seek, 0, 2)
+
+ def test_iter(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({'bananas': 'bananas'}, 'multipart/form-data; boundary=foobar')
+ self.assertEqual(list(body), [
+ '--foobar\r\n',
+ 'Content-Disposition: form-data; name="bananas"\r\n',
+ '\r\n',
+ 'bananas\r\n',
+ '--foobar--',
+ ])
+
+ def test_readline(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({'bananas': 'bananas'}, 'multipart/form-data; boundary=foobar')
+ self.assertEqual(body.readline(), '--foobar\r\n')
+ self.assertEqual(body.readline(), 'Content-Disposition: form-data; name="bananas"\r\n')
+ self.assertEqual(body.readline(), '\r\n')
+ self.assertEqual(body.readline(), 'bananas\r\n')
+ self.assertEqual(body.readline(), '--foobar--')
+ # subsequent calls to readline will return ''
+
+ def test_read_bad_content_type(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({'bananas': 'bananas'}, 'application/jibberjabber')
+ self.assertRaises(AssertionError, body.read)
+
+ def test_read_urlencoded(self):
+ from webob.request import FakeCGIBody
+ body = FakeCGIBody({'bananas': 'bananas'}, 'application/x-www-form-urlencoded')
+ self.assertEqual(body.read(), 'bananas=bananas')
+
+
+class Test_cgi_FieldStorage__repr__patch(unittest.TestCase):
+ def _callFUT(self, fake):
+ from webob.request import _cgi_FieldStorage__repr__patch
+ return _cgi_FieldStorage__repr__patch(fake)
+
+ def test_with_file(self):
+ class Fake(object):
+ name = 'name'
+ file = 'file'
+ filename = 'filename'
+ value = 'value'
+ fake = Fake()
+ result = self._callFUT(fake)
+ self.assertEqual(result, "FieldStorage('name', 'filename')")
+
+ def test_without_file(self):
+ class Fake(object):
+ name = 'name'
+ file = None
+ filename = 'filename'
+ value = 'value'
+ fake = Fake()
+ result = self._callFUT(fake)
+ self.assertEqual(result, "FieldStorage('name', 'filename', 'value')")
diff --git a/tests/test_response.py b/tests/test_response.py
index 06f79b0..37d042e 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -1,8 +1,13 @@
import sys
+import zlib
if sys.version >= '2.7':
from io import BytesIO as StringIO
else:
from cStringIO import StringIO
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
from nose.tools import eq_, ok_, assert_raises
@@ -105,6 +110,209 @@ def test_HEAD_closes():
eq_(res.body, '')
ok_(app_iter.closed)
+def test_HEAD_conditional_response_returns_empty_response():
+ from webob.response import EmptyResponse
+ req = Request.blank('/')
+ req.method = 'HEAD'
+ res = Response(request=req, conditional_response=True)
+ class FakeRequest:
+ method = 'HEAD'
+ if_none_match = 'none'
+ if_modified_since = False
+ range = False
+ def __init__(self, env):
+ self.env = env
+ def start_response(status, headerlist):
+ pass
+ res.RequestClass = FakeRequest
+ result = res({}, start_response)
+ ok_(isinstance(result, EmptyResponse))
+
+def test_HEAD_conditional_response_range_empty_response():
+ from webob.response import EmptyResponse
+ req = Request.blank('/')
+ req.method = 'HEAD'
+ res = Response(request=req, conditional_response=True)
+ res.status_int = 200
+ res.body = 'Are we not men?'
+ res.content_length = len(res.body)
+ class FakeRequest:
+ method = 'HEAD'
+ if_none_match = 'none'
+ if_modified_since = False
+ def __init__(self, env):
+ self.env = env
+ self.range = self # simulate inner api
+ self.if_range = self
+ def content_range(self, length):
+ """range attr"""
+ class Range:
+ start = 4
+ stop = 5
+ return Range
+ def match_response(self, res):
+ """if_range_match attr"""
+ return True
+ def start_response(status, headerlist):
+ pass
+ res.RequestClass = FakeRequest
+ result = res({}, start_response)
+ ok_(isinstance(result, EmptyResponse), result)
+
+def test_conditional_response_if_none_match_false():
+ req = Request.blank('/', if_none_match='foo')
+ resp = Response(app_iter=['foo\n'],
+ conditional_response=True, etag='foo')
+ resp = req.get_response(resp)
+ eq_(resp.status_int, 304)
+
+def test_conditional_response_if_none_match_true():
+ req = Request.blank('/', if_none_match='foo')
+ resp = Response(app_iter=['foo\n'],
+ conditional_response=True, etag='bar')
+ resp = req.get_response(resp)
+ eq_(resp.status_int, 200)
+
+def test_conditional_response_if_modified_since_false():
+ from datetime import datetime, timedelta
+ req = Request.blank('/', if_modified_since=datetime(2011, 3, 17, 13, 0, 0))
+ resp = Response(app_iter=['foo\n'], conditional_response=True,
+ last_modified=req.if_modified_since-timedelta(seconds=1))
+ resp = req.get_response(resp)
+ eq_(resp.status_int, 304)
+
+def test_conditional_response_if_modified_since_true():
+ from datetime import datetime, timedelta
+ req = Request.blank('/', if_modified_since=datetime(2011, 3, 17, 13, 0, 0))
+ resp = Response(app_iter=['foo\n'], conditional_response=True,
+ last_modified=req.if_modified_since+timedelta(seconds=1))
+ resp = req.get_response(resp)
+ eq_(resp.status_int, 200)
+
+def test_conditional_response_range_not_satisfiable_response():
+ req = Request.blank('/', range='bytes=100-200')
+ resp = Response(app_iter=['foo\n'], content_length=4,
+ conditional_response=True)
+ resp = req.get_response(resp)
+ eq_(resp.status_int, 416)
+ eq_(resp.content_range.start, None)
+ eq_(resp.content_range.stop, None)
+ eq_(resp.content_range.length, 4)
+ eq_(resp.body, 'Requested range not satisfiable: bytes=100-200')
+
+def test_HEAD_conditional_response_range_not_satisfiable_response():
+ req = Request.blank('/', method='HEAD', range='bytes=100-200')
+ resp = Response(app_iter=['foo\n'], content_length=4,
+ conditional_response=True)
+ resp = req.get_response(resp)
+ eq_(resp.status_int, 416)
+ eq_(resp.content_range.start, None)
+ eq_(resp.content_range.stop, None)
+ eq_(resp.content_range.length, 4)
+ eq_(resp.body, '')
+
+def test_del_environ():
+ res = Response()
+ res.environ = {'yo': 'mama'}
+ eq_(res.environ, {'yo': 'mama'})
+ del res.environ
+ eq_(res.environ, None)
+ eq_(res.request, None)
+
+def test_set_request_environ():
+ res = Response()
+ class FakeRequest:
+ environ = {'jo': 'mama'}
+ res.request = FakeRequest
+ eq_(res.environ, {'jo': 'mama'})
+ eq_(res.request, FakeRequest)
+ res.environ = None
+ eq_(res.environ, None)
+ eq_(res.request, None)
+
+def test_del_request():
+ res = Response()
+ class FakeRequest:
+ environ = {}
+ res.request = FakeRequest
+ del res.request
+ eq_(res.environ, None)
+ eq_(res.request, None)
+
+def test_set_environ_via_request_subterfuge():
+ class FakeRequest:
+ def __init__(self, env):
+ self.environ = env
+ res = Response()
+ res.RequestClass = FakeRequest
+ res.request = {'action': 'dwim'}
+ eq_(res.environ, {'action': 'dwim'})
+ ok_(isinstance(res.request, FakeRequest))
+ eq_(res.request.environ, res.environ)
+
+def test_set_request():
+ res = Response()
+ class FakeRequest:
+ environ = {'foo': 'bar'}
+ res.request = FakeRequest
+ eq_(res.request, FakeRequest)
+ eq_(res.environ, FakeRequest.environ)
+ res.request = None
+ eq_(res.environ, None)
+ eq_(res.request, None)
+
+def test_md5_etag():
+ res = Response()
+ res.body = """\
+In A.D. 2101
+War was beginning.
+Captain: What happen ?
+Mechanic: Somebody set up us the bomb.
+Operator: We get signal.
+Captain: What !
+Operator: Main screen turn on.
+Captain: It's You !!
+Cats: How are you gentlemen !!
+Cats: All your base are belong to us.
+Cats: You are on the way to destruction.
+Captain: What you say !!
+Cats: You have no chance to survive make your time.
+Cats: HA HA HA HA ....
+Captain: Take off every 'zig' !!
+Captain: You know what you doing.
+Captain: Move 'zig'.
+Captain: For great justice."""
+ res.md5_etag()
+ ok_(res.etag)
+ ok_('\n' not in res.etag)
+ eq_(res.etag,
+ md5(res.body).digest().encode('base64').replace('\n', '').strip('='))
+ eq_(res.content_md5, None)
+
+def test_md5_etag_set_content_md5():
+ res = Response()
+ b = 'The quick brown fox jumps over the lazy dog'
+ res.md5_etag(b, set_content_md5=True)
+ ok_(res.content_md5,
+ md5(b).digest().encode('base64').replace('\n', '').strip('='))
+
+def test_decode_content_defaults_to_identity():
+ res = Response()
+ res.body = 'There be dragons'
+ res.decode_content()
+ eq_(res.body, 'There be dragons')
+
+def test_decode_content_with_deflate():
+ res = Response()
+ b = 'Hey Hey Hey'
+ # Simulate inflate by chopping the headers off
+ # the gzip encoded data
+ res.body = zlib.compress(b)[2:-4]
+ res.content_encoding = 'deflate'
+ res.decode_content()
+ eq_(res.body, b)
+ eq_(res.content_encoding, None)
+
def test_content_length():
r0 = Response('x'*10, content_length=10)
@@ -159,6 +367,13 @@ def test_app_iter_range():
eq_(list(res.content_range), [2,5,6])
eq_(res.body, '234', 'body=%r; app_iter=%r' % (res.body, app_iter))
+def test_app_iter_range_inner_method():
+ class FakeAppIter:
+ def app_iter_range(self, start, stop):
+ return 'you win', start, stop
+ res = Response(app_iter=FakeAppIter())
+ eq_(res.app_iter_range(30, 40), ('you win', 30, 40))
+
def test_content_type_in_headerlist():
# Couldn't manage to clone Response in order to modify class
# attributes safely. Shouldn't classes be fresh imported for every
@@ -213,3 +428,608 @@ def test_set_headerlist():
eq_(res.headerlist, [('Content-Type', 'text/html; charset=UTF-8')])
del res.headerlist
eq_(res.headerlist, [])
+
+def test_request_uri_no_script_name():
+ from webob.response import _request_uri
+ environ = {
+ 'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'test.com',
+ 'SCRIPT_NAME': '/foobar',
+ }
+ eq_(_request_uri(environ), 'http://test.com/foobar')
+
+def test_request_uri_https():
+ from webob.response import _request_uri
+ environ = {
+ 'wsgi.url_scheme': 'https',
+ 'SERVER_NAME': 'test.com',
+ 'SERVER_PORT': '443',
+ 'SCRIPT_NAME': '/foobar',
+ }
+ eq_(_request_uri(environ), 'https://test.com/foobar')
+
+def test_app_iter_range_starts_after_iter_end():
+ from webob.response import AppIterRange
+ range = AppIterRange(iter([]), start=1, stop=1)
+ eq_(list(range), [])
+
+def test_response_file_body_flush_is_no_op():
+ from webob.response import ResponseBodyFile
+ rbo = ResponseBodyFile(None)
+ rbo.flush()
+
+def test_response_file_body_writelines():
+ from webob.response import ResponseBodyFile
+ class FakeResponse:
+ pass
+ res = FakeResponse()
+ res._app_iter = res.app_iter = ['foo']
+ rbo = ResponseBodyFile(res)
+ rbo.writelines(['bar', 'baz'])
+ eq_(res.app_iter, ['foo', 'bar', 'baz'])
+
+def test_response_file_body_write_non_str():
+ from webob.response import ResponseBodyFile
+ class FakeResponse:
+ pass
+ res = FakeResponse()
+ rbo = ResponseBodyFile(res)
+ assert_raises(TypeError, rbo.write, object())
+
+def test_response_file_body_write_empty_app_iter():
+ from webob.response import ResponseBodyFile
+ class FakeResponse:
+ pass
+ res = FakeResponse()
+ res._app_iter = res.app_iter = None
+ res.body = 'foo'
+ rbo = ResponseBodyFile(res)
+ rbo.write('baz')
+ eq_(res.app_iter, ['foo', 'baz'])
+
+def test_response_file_body_write_empty_body():
+ from webob.response import ResponseBodyFile
+ class FakeResponse:
+ body = ''
+ res = FakeResponse()
+ res._app_iter = res.app_iter = None
+ rbo = ResponseBodyFile(res)
+ rbo.write('baz')
+ eq_(res.app_iter, ['baz'])
+
+def test_response_file_body_close_not_implemented():
+ from webob.response import ResponseBodyFile
+ rbo = ResponseBodyFile(None)
+ assert_raises(NotImplementedError, rbo.close)
+
+def test_response_file_body_repr():
+ from webob.response import ResponseBodyFile
+ rbo = ResponseBodyFile('yo')
+ eq_(repr(rbo), "<body_file for 'yo'>")
+
+def test_body_get_is_none():
+ res = Response()
+ res._body = None
+ res._app_iter = None
+ assert_raises(TypeError, Response, app_iter=iter(['a']),
+ body="somebody")
+ assert_raises(AttributeError, res.__getattribute__, 'body')
+
+def test_body_get_is_unicode_notverylong():
+ res = Response()
+ res._app_iter = u'foo'
+ res._body = None
+ assert_raises(ValueError, res.__getattribute__, 'body')
+
+def test_body_get_is_unicode_verylong():
+ res = Response()
+ res._app_iter = u'x' * 51
+ res._body = None
+ assert_raises(ValueError, res.__getattribute__, 'body')
+
+def test_body_set_not_unicode_or_str():
+ res = Response()
+ assert_raises(TypeError, res.__setattr__, 'body', object())
+
+def test_body_set_unicode():
+ res = Response()
+ assert_raises(TypeError, res.__setattr__, 'body', u'abc')
+
+def test_body_set_under_body_doesnt_exist():
+ res = Response()
+ del res._body
+ res.body = 'abc'
+ eq_(res._body, 'abc')
+ eq_(res.content_length, 3)
+ eq_(res._app_iter, None)
+
+def test_body_del():
+ res = Response()
+ res._body = '123'
+ res.content_length = 3
+ res._app_iter = ()
+ del res.body
+ eq_(res._body, None)
+ eq_(res.content_length, None)
+ eq_(res._app_iter, None)
+
+def test_unicode_body_get_no_charset():
+ res = Response()
+ res.charset = None
+ assert_raises(AttributeError, res.__getattribute__, 'unicode_body')
+
+def test_unicode_body_get_decode():
+ res = Response()
+ res.charset = 'utf-8'
+ res.body = 'La Pe\xc3\xb1a'
+ eq_(res.unicode_body, unicode('La Pe\xc3\xb1a', 'utf-8'))
+
+def test_unicode_body_set_no_charset():
+ res = Response()
+ res.charset = None
+ assert_raises(AttributeError, res.__setattr__, 'unicode_body', 'abc')
+
+def test_unicode_body_set_not_unicode():
+ res = Response()
+ res.charset = 'utf-8'
+ assert_raises(TypeError, res.__setattr__, 'unicode_body',
+ 'La Pe\xc3\xb1a')
+
+def test_unicode_body_del():
+ res = Response()
+ res._body = '123'
+ res.content_length = 3
+ res._app_iter = ()
+ del res.unicode_body
+ eq_(res._body, None)
+ eq_(res.content_length, None)
+ eq_(res._app_iter, None)
+
+def test_body_file_del():
+ res = Response()
+ res._body = '123'
+ res.content_length = 3
+ res._app_iter = ()
+ del res.body_file
+ eq_(res._body, None)
+ eq_(res.content_length, None)
+ eq_(res._app_iter, None)
+
+def test_write_unicode():
+ res = Response()
+ res.unicode_body = unicode('La Pe\xc3\xb1a', 'utf-8')
+ res.write(u'a')
+ eq_(res.unicode_body, unicode('La Pe\xc3\xb1aa', 'utf-8'))
+
+def test_write_text():
+ res = Response()
+ res.body = 'abc'
+ res.write(u'a')
+ eq_(res.unicode_body, 'abca')
+
+def test_app_iter_get_app_iter_is_None():
+ res = Response()
+ res._app_iter = None
+ res._body = None
+ assert_raises(AttributeError, res.__getattribute__, 'app_iter')
+
+def test_app_iter_del():
+ res = Response()
+ res.content_length = 3
+ res._app_iter = ['123']
+ del res.app_iter
+ eq_(res._app_iter, None)
+ eq_(res._body, None)
+ eq_(res.content_length, None)
+
+
+def test_charset_set_charset_is_None():
+ res = Response()
+ res.charset = 'utf-8'
+ res._app_iter = ['123']
+ del res.app_iter
+ eq_(res._app_iter, None)
+ eq_(res._body, None)
+ eq_(res.content_length, None)
+
+def test_charset_set_no_content_type_header():
+ res = Response()
+ res.headers.pop('Content-Type', None)
+ assert_raises(AttributeError, res.__setattr__, 'charset', 'utf-8')
+
+def test_charset_del_no_content_type_header():
+ res = Response()
+ res.headers.pop('Content-Type', None)
+ eq_(res._charset__del(), None)
+
+def test_content_type_params_get_no_semicolon_in_content_type_header():
+ res = Response()
+ res.headers['Content-Type'] = 'foo'
+ eq_(res.content_type_params, {})
+
+def test_content_type_params_get_semicolon_in_content_type_header():
+ res = Response()
+ res.headers['Content-Type'] = 'foo;encoding=utf-8'
+ eq_(res.content_type_params, {'encoding':'utf-8'})
+
+def test_content_type_params_set_value_dict_empty():
+ res = Response()
+ res.headers['Content-Type'] = 'foo;bar'
+ res.content_type_params = None
+ eq_(res.headers['Content-Type'], 'foo')
+
+def test_content_type_params_set_ok_param_quoting():
+ res = Response()
+ res.content_type_params = {'a':''}
+ eq_(res.headers['Content-Type'], 'text/html; a=""')
+
+def test_set_cookie_overwrite():
+ res = Response()
+ res.set_cookie('a', '1')
+ res.set_cookie('a', '2', overwrite=True)
+ eq_(res.headerlist[-1], ('Set-Cookie', 'a=2; Path=/'))
+
+def test_set_cookie_value_is_None():
+ res = Response()
+ res.set_cookie('a', None)
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 4
+ val.sort()
+ eq_(val[0], 'Max-Age=0')
+ eq_(val[1], 'Path=/')
+ eq_(val[2], 'a=')
+ assert val[3].startswith('expires')
+
+def test_set_cookie_expires_is_None_and_max_age_is_int():
+ res = Response()
+ res.set_cookie('a', '1', max_age=100)
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 4
+ val.sort()
+ eq_(val[0], 'Max-Age=100')
+ eq_(val[1], 'Path=/')
+ eq_(val[2], 'a=1')
+ assert val[3].startswith('expires')
+
+def test_set_cookie_expires_is_None_and_max_age_is_timedelta():
+ from datetime import timedelta
+ res = Response()
+ res.set_cookie('a', '1', max_age=timedelta(seconds=100))
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 4
+ val.sort()
+ eq_(val[0], 'Max-Age=100')
+ eq_(val[1], 'Path=/')
+ eq_(val[2], 'a=1')
+ assert val[3].startswith('expires')
+
+def test_set_cookie_expires_is_not_None_and_max_age_is_None():
+ import datetime
+ res = Response()
+ then = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+ res.set_cookie('a', '1', expires=then)
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 4
+ val.sort()
+ ok_(val[0] in ('Max-Age=86399', 'Max-Age=86400'))
+ eq_(val[1], 'Path=/')
+ eq_(val[2], 'a=1')
+ assert val[3].startswith('expires')
+
+def test_set_cookie_value_is_unicode():
+ res = Response()
+ val = unicode('La Pe\xc3\xb1a', 'utf-8')
+ res.set_cookie('a', val)
+ eq_(res.headerlist[-1], (r'Set-Cookie', 'a="La Pe\\303\\261a"; Path=/'))
+
+def test_delete_cookie():
+ res = Response()
+ res.headers['Set-Cookie'] = 'a=2; Path=/'
+ res.delete_cookie('a')
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 4
+ val.sort()
+ eq_(val[0], 'Max-Age=0')
+ eq_(val[1], 'Path=/')
+ eq_(val[2], 'a=')
+ assert val[3].startswith('expires')
+
+def test_delete_cookie_with_path():
+ res = Response()
+ res.headers['Set-Cookie'] = 'a=2; Path=/'
+ res.delete_cookie('a', path='/abc')
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 4
+ val.sort()
+ eq_(val[0], 'Max-Age=0')
+ eq_(val[1], 'Path=/abc')
+ eq_(val[2], 'a=')
+ assert val[3].startswith('expires')
+
+def test_delete_cookie_with_domain():
+ res = Response()
+ res.headers['Set-Cookie'] = 'a=2; Path=/'
+ res.delete_cookie('a', path='/abc', domain='example.com')
+ eq_(res.headerlist[-1][0], 'Set-Cookie')
+ val = [ x.strip() for x in res.headerlist[-1][1].split(';')]
+ assert len(val) == 5
+ val.sort()
+ eq_(val[0], 'Domain=example.com')
+ eq_(val[1], 'Max-Age=0')
+ eq_(val[2], 'Path=/abc')
+ eq_(val[3], 'a=')
+ assert val[4].startswith('expires')
+
+def test_unset_cookie_not_existing_and_not_strict():
+ res = Response()
+ result = res.unset_cookie('a', strict=False)
+ assert result is None
+
+def test_unset_cookie_not_existing_and_strict():
+ res = Response()
+ assert_raises(KeyError, res.unset_cookie, 'a')
+
+def test_unset_cookie_key_in_cookies():
+ res = Response()
+ res.headers.add('Set-Cookie', 'a=2; Path=/')
+ res.headers.add('Set-Cookie', 'b=3; Path=/')
+ res.unset_cookie('a')
+ eq_(res.headers.getall('Set-Cookie'), ['b=3; Path=/'])
+
+def test_merge_cookies_no_set_cookie():
+ res = Response()
+ result = res.merge_cookies('abc')
+ eq_(result, 'abc')
+
+def test_merge_cookies_resp_is_Response():
+ inner_res = Response()
+ res = Response()
+ res.set_cookie('a', '1')
+ result = res.merge_cookies(inner_res)
+ eq_(result.headers.getall('Set-Cookie'), ['a=1; Path=/'])
+
+def test_merge_cookies_resp_is_wsgi_callable():
+ L = []
+ def dummy_wsgi_callable(environ, start_response):
+ L.append((environ, start_response))
+ return 'abc'
+ res = Response()
+ res.set_cookie('a', '1')
+ wsgiapp = res.merge_cookies(dummy_wsgi_callable)
+ environ = {}
+ def dummy_start_response(status, headers, exc_info=None):
+ eq_(headers, [('Set-Cookie', 'a=1; Path=/')])
+ result = wsgiapp(environ, dummy_start_response)
+ assert result == 'abc'
+ assert len(L) == 1
+ L[0][1]('200 OK', []) # invoke dummy_start_response assertion
+
+def test_body_get_body_is_None_len_app_iter_is_zero():
+ res = Response()
+ res._app_iter = StringIO()
+ res._body = None
+ result = res.body
+ eq_(result, '')
+
+def test_body_set_AttributeError_edgecase():
+ res = Response()
+ del res._app_iter
+ del res._body
+ res.body = 'abc'
+ eq_(res._body, 'abc')
+ eq_(res.content_length, 3)
+ eq_(res._app_iter, None)
+
+def test_cache_control_get():
+ res = Response()
+ eq_(repr(res.cache_control), "<CacheControl ''>")
+ eq_(res.cache_control.max_age, None)
+
+def test_location():
+ # covers webob/response.py:934-938
+ res = Response()
+ res.location = '/test.html'
+ eq_(res.location, '/test.html')
+ req = Request.blank('/')
+ eq_(req.get_response(res).location, 'http://localhost/test.html')
+ res.location = '/test2.html'
+ eq_(req.get_response(res).location, 'http://localhost/test2.html')
+
+def test_request_uri_http():
+ # covers webob/response.py:1152
+ from webob.response import _request_uri
+ environ = {
+ 'wsgi.url_scheme': 'http',
+ 'SERVER_NAME': 'test.com',
+ 'SERVER_PORT': '80',
+ 'SCRIPT_NAME': '/foobar',
+ }
+ eq_(_request_uri(environ), 'http://test.com/foobar')
+
+def test_request_uri_no_script_name2():
+ # covers webob/response.py:1160
+ # There is a test_request_uri_no_script_name in test_response.py, but it
+ # sets SCRIPT_NAME.
+ from webob.response import _request_uri
+ environ = {
+ 'wsgi.url_scheme': 'http',
+ 'HTTP_HOST': 'test.com',
+ 'PATH_INFO': '/foobar',
+ }
+ eq_(_request_uri(environ), 'http://test.com/foobar')
+
+def test_cache_control_object_max_age_ten():
+ res = Response()
+ res.cache_control.max_age = 10
+ eq_(repr(res.cache_control), "<CacheControl 'max-age=10'>")
+ eq_(res.headers['cache-control'], 'max-age=10')
+
+def test_cache_control_set_object_error():
+ res = Response()
+ assert_raises(AttributeError, setattr, res.cache_control, 'max_stale', 10)
+
+def test_cache_expires_set():
+ res = Response()
+ res.cache_expires = True
+ eq_(repr(res.cache_control),
+ "<CacheControl 'max-age=0, must-revalidate, no-cache, no-store'>")
+
+def test_status_int_set():
+ res = Response()
+ res.status_int = 400
+ eq_(res._status, '400 Bad Request')
+
+def test_cache_control_set_dict():
+ res = Response()
+ res.cache_control = {'a':'b'}
+ eq_(repr(res.cache_control), "<CacheControl 'a=b'>")
+
+def test_cache_control_set_None():
+ res = Response()
+ res.cache_control = None
+ eq_(repr(res.cache_control), "<CacheControl ''>")
+
+def test_cache_control_set_unicode():
+ res = Response()
+ res.cache_control = u'abc'
+ eq_(repr(res.cache_control), "<CacheControl 'abc'>")
+
+def test_cache_control_set_control_obj_is_not_None():
+ class DummyCacheControl(object):
+ def __init__(self):
+ self.header_value = 1
+ self.properties = {'bleh':1}
+ res = Response()
+ res._cache_control_obj = DummyCacheControl()
+ res.cache_control = {}
+ eq_(res.cache_control.properties, {})
+
+def test_cache_control_del():
+ res = Response()
+ del res.cache_control
+ eq_(repr(res.cache_control), "<CacheControl ''>")
+
+def test_body_file_get():
+ res = Response()
+ result = res.body_file
+ from webob.response import ResponseBodyFile
+ eq_(result.__class__, ResponseBodyFile)
+
+def test_body_file_write_no_charset():
+ from webob.response import ResponseBodyFile
+ class FakeReponse:
+ charset = None
+ rbo = ResponseBodyFile(FakeReponse())
+ assert_raises(TypeError, rbo.write, u'foo')
+
+def test_body_file_write_unicode_encodes():
+ from webob.response import ResponseBodyFile
+ class FakeReponse:
+ charset = 'utf-8'
+ _app_iter = app_iter = []
+ s = unicode('La Pe\xc3\xb1a', 'utf-8')
+ res = FakeReponse()
+ rbo = ResponseBodyFile(res)
+ rbo.write(s)
+ eq_(res.app_iter, ['La Pe\xc3\xb1a'])
+
+def test_repr():
+ res = Response()
+ ok_(repr(res).endswith('200 OK>'))
+
+def test_cache_expires_set_timedelta():
+ res = Response()
+ from datetime import timedelta
+ delta = timedelta(seconds=60)
+ res.cache_expires(seconds=delta)
+ eq_(res.cache_control.max_age, 60)
+
+def test_cache_expires_set_int():
+ res = Response()
+ res.cache_expires(seconds=60)
+ eq_(res.cache_control.max_age, 60)
+
+def test_cache_expires_set_None():
+ res = Response()
+ res.cache_expires(seconds=None, a=1)
+ eq_(res.cache_control.a, 1)
+
+def test_cache_expires_set_zero():
+ res = Response()
+ res.cache_expires(seconds=0)
+ eq_(res.cache_control.no_store, True)
+ eq_(res.cache_control.no_cache, '*')
+ eq_(res.cache_control.must_revalidate, True)
+ eq_(res.cache_control.max_age, 0)
+ eq_(res.cache_control.post_check, 0)
+
+def test_encode_content_unknown():
+ res = Response()
+ assert_raises(AssertionError, res.encode_content, 'badencoding')
+
+def test_encode_content_identity():
+ res = Response()
+ result = res.encode_content('identity')
+ eq_(result, None)
+
+def test_encode_content_gzip_already_gzipped():
+ res = Response()
+ res.content_encoding = 'gzip'
+ result = res.encode_content('gzip')
+ eq_(result, None)
+
+def test_encode_content_gzip_notyet_gzipped():
+ res = Response()
+ res.app_iter = StringIO('foo')
+ result = res.encode_content('gzip')
+ eq_(result, None)
+ eq_(res.content_length, 23)
+ eq_(res.app_iter, ['\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff', '',
+ 'K\xcb\xcf\x07\x00', '!es\x8c\x03\x00\x00\x00'])
+
+def test_encode_content_gzip_notyet_gzipped_lazy():
+ res = Response()
+ res.app_iter = StringIO('foo')
+ result = res.encode_content('gzip', lazy=True)
+ eq_(result, None)
+ eq_(res.content_length, None)
+ eq_(list(res.app_iter), ['\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff', '',
+ 'K\xcb\xcf\x07\x00', '!es\x8c\x03\x00\x00\x00'])
+
+def test_decode_content_identity():
+ res = Response()
+ res.content_encoding = 'identity'
+ result = res.decode_content()
+ eq_(result, None)
+
+def test_decode_content_weird():
+ res = Response()
+ res.content_encoding = 'weird'
+ assert_raises(ValueError, res.decode_content)
+
+def test_decode_content_gzip():
+ from gzip import GzipFile
+ io = StringIO()
+ gzip_f = GzipFile(filename='', mode='w', fileobj=io)
+ gzip_f.write('abc')
+ gzip_f.close()
+ body = io.getvalue()
+ res = Response()
+ res.content_encoding = 'gzip'
+ res.body = body
+ res.decode_content()
+ eq_(res.body, 'abc')
+
+def test__abs_headerlist_location_with_scheme():
+ res = Response()
+ res.content_encoding = 'gzip'
+ res.headerlist = [('Location', 'http:')]
+ result = res._abs_headerlist({})
+ eq_(result, [('Location', 'http:')])
+
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..9cd35d2
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,39 @@
+[tox]
+envlist =
+ py24,py25,py26,py27,jython,pypy,cover
+
+[testenv]
+commands =
+ python setup.py test -q
+deps =
+ repoze.profile
+ Tempita
+ WSGIProxy
+ WebTest
+ dtopt
+ nose
+
+[testenv:jython]
+commands =
+ jython setup.py test -q
+
+[testenv:cover]
+basepython =
+ python2.6
+commands =
+ python setup.py nosetests --with-xunit --with-xcoverage
+deps =
+ repoze.profile
+ Tempita
+ WSGIProxy
+ WebTest
+ dtopt
+ nose
+ coverage==3.4
+ nosexcover
+
+# we separate coverage into its own testenv because a) "last run wins" wrt
+# cobertura jenkins reporting and b) pypy and jython can't handle any
+# combination of versions of coverage and nosexcover that i can find.
+# coverage <3.4 is required by nosexcover 1.0.4.
+
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: