View
@@ -20,14 +20,14 @@ parse HTTP responses.
Support and Documentation
-------------------------
See the `WebOb Documentation website <http://webob.readthedocs.org/>`_ to view
See the `WebOb Documentation website <https://webob.readthedocs.io/>`_ to view
documentation, report bugs, and obtain support.
License
-------
WebOb is offered under the `MIT-license
<http://webob.readthedocs.org/en/latest/license.html>`_.
<https://webob.readthedocs.io/en/latest/license.html>`_.
Authors
-------
View
@@ -56,6 +56,8 @@
htmlhelp_basename = 'WebObdoc'
html_use_smartypants = False
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
View
@@ -44,8 +44,8 @@ request (including the path and query string), a file-like object for
the request body, and a variety of custom keys. You can always access
the environ with ``req.environ``.
Some of the most important/interesting attributes of a request
object:
Some of the most important and interesting attributes of a request object are
the following:
``req.method``:
The request method, e.g., ``'GET'``, ``'POST'``
@@ -74,25 +74,24 @@ object:
A simple dictionary of all the cookies.
``req.headers``:
A dictionary of all the headers. This is dictionary is case-insensitive.
A dictionary of all the headers. This dictionary is case-insensitive.
``req.urlvars`` and ``req.urlargs``:
``req.urlvars`` is the keyword parameters associated with the
request URL. ``req.urlargs`` are the positional parameters.
These are set by products like `Routes
<http://routes.groovie.org/>`_ and `Selector
<http://lukearno.com/projects/selector/>`_.
<http://routes.readthedocs.io/en/latest/>`_ and `Selector
<https://github.com/lukearno/selector>`_.
.. _`dictionary-like object`: #multidict
Also, for standard HTTP request headers there are usually attributes,
for instance: ``req.accept_language``, ``req.content_length``,
``req.user_agent``, as an example. These properties expose the
*parsed* form of each header, for whatever parsing makes sense. For
instance, ``req.if_modified_since`` returns a `datetime
<http://python.org/doc/current/lib/datetime-datetime.html>`_ object
(or None if the header is was not provided). Details are in the
`Request reference <class-webob.Request.html>`_.
Also for standard HTTP request headers, there are usually attributes, e.g.,
``req.accept_language``, ``req.content_length``, and ``req.user_agent``. These
properties expose the *parsed* form of each header, for whatever parsing makes
sense. For instance, ``req.if_modified_since`` returns a `datetime
<http://python.org/doc/current/lib/datetime-datetime.html>`_ object (or
``None`` if the header is was not provided). Details are in the `Request
reference <class-webob.Request.html>`_.
URLs
----
@@ -104,18 +103,18 @@ is mounted at ``http://localhost/app-root``.
``req.url``:
The full request URL, with query string, e.g.,
``'http://localhost/app-root/doc?article_id=10'``
``'http://localhost/app-root/doc?article_id=10'``.
``req.application_url``:
The URL of the application (just the SCRIPT_NAME portion of the
path, not PATH_INFO). E.g., ``'http://localhost/app-root'``
The URL of the application (just the ``SCRIPT_NAME`` portion of the
path, not ``PATH_INFO``), e.g., ``'http://localhost/app-root'``.
``req.host_url``:
The URL with the host, e.g., ``'http://localhost'``
The URL with the host, e.g., ``'http://localhost'``.
``req.relative_url(url, to_application=False)``:
Gives a URL, relative to the current URL. If ``to_application``
is True, then resolves it relative to ``req.application_url``.
is True, then the URL is resolved relative to ``req.application_url``.
Methods
-------
@@ -149,9 +148,7 @@ you subclass ``Request`` you can also set ``charset`` as a class-level
attribute.
If it is set, then ``req.POST``, ``req.GET``, ``req.params``, and
``req.cookies`` will contain unicode strings. Each has a
corresponding ``req.str_*`` (like ``req.str_POST``) that is always
``str`` and never unicode.
``req.cookies`` will contain unicode strings.
Response
========
@@ -180,7 +177,7 @@ WSGI):
to ``app_iter``).
Everything else in the object derives from this underlying state.
Here's the highlights:
Here are the highlights:
``response.content_type``:
The content type *not* including the ``charset`` parameter.
@@ -210,7 +207,7 @@ Here's the highlights:
the cookie value to ``''``.
``response.cache_expires(seconds=0)``:
This makes this response cachable for the given number of seconds,
This makes this response cacheable for the given number of seconds,
or if ``seconds`` is 0 then the response is uncacheable (this also
sets the ``Expires`` header).
@@ -234,15 +231,15 @@ Instantiating the Response
Of course most of the time you just want to *make* a response.
Generally any attribute of the response can be passed in as a keyword
argument to the class; e.g.:
argument to the class, e.g.:
.. code-block:: python
response = Response(body='hello world!', content_type='text/plain')
The status defaults to ``'200 OK'``. The content_type does not
default to anything, though if you subclass ``Response`` and set
``default_content_type`` you can override this behavior.
default to anything, although if you subclass ``Response`` and set
``default_content_type``, you can override this behavior.
Exceptions
==========
@@ -267,33 +264,33 @@ You can use this like:
.. code-block:: python
try:
... stuff ...
# ... stuff ...
raise HTTPNotFound('No such resource')
except HTTPException, e:
return e(environ, start_response)
The exceptions are still WSGI applications, but you cannot set
attributes like ``content_type``, ``charset``, etc. on these exception
attributes like ``content_type``, ``charset``, etc., on these exception
objects.
Multidict
=========
Several parts of WebOb use a "multidict"; this is a dictionary where a
Several parts of WebOb use a "multidict", which is a dictionary where a
key can have multiple values. The quintessential example is a query
string like ``?pref=red&pref=blue``; the ``pref`` variable has two
values: ``red`` and ``blue``.
string like ``?pref=red&pref=blue``. The ``pref`` variable has two
values, ``red`` and ``blue``.
In a multidict, when you do ``request.GET['pref']`` you'll get back
In a multidict, when you do ``request.GET['pref']``, you'll get back
only ``'blue'`` (the last value of ``pref``). Sometimes returning a
string, and sometimes returning a list, is the cause of frequent
string and other times returning a list is a cause of frequent
exceptions. If you want *all* the values back, use
``request.GET.getall('pref')``. If you want to be sure there is *one
and only one* value, use ``request.GET.getone('pref')``, which will
raise an exception if there is zero or more than one value for
``pref``.
When you use operations like ``request.GET.items()`` you'll get back
When you use operations like ``request.GET.items()``, you'll get back
something like ``[('pref', 'red'), ('pref', 'blue')]``. All the
key/value pairs will show up. Similarly ``request.GET.keys()``
returns ``['pref', 'pref']``. Multidict is a view on a list of
@@ -302,11 +299,13 @@ tuples; all the keys are ordered, and all the values are ordered.
Example
=======
The `file-serving example <file-example.html>`_ shows how to do more
The `file-serving example <file-example>`_ shows how to do more
advanced HTTP techniques, while the `comment middleware example
<comment-example.html>`_ shows middleware. For applications it's more
reasonable to use WebOb in the context of a larger framework. `Pylons
<http://pylonshq.com>`_ uses WebOb in 0.9.7+.
<comment-example>`_ shows middleware. For applications, it's more
reasonable to use WebOb in the context of a larger framework. `Pyramid
<https://trypyramid.com>`_, and its predecessor `Pylons
<http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_,
both use WebOb.
.. toctree::
:maxdepth: 1
@@ -324,29 +323,29 @@ Change History
:maxdepth: 1
whatsnew-1.5
whatsnew-1.6
changes
Status & License
================
Status and License
==================
WebOb is an extraction and refinement of pieces from `Paste
<http://pythonpaste.org/>`_. It is under active development. Discussion
should happen on the `Pylons-discuss maillist
<http://groups.google.com/group/pylons-discuss>`_, and bugs can go on the
`issue tracker <https://github.com/Pylons/webob/issues>`_. It was originally
written by `Ian Bicking <http://ianbicking.org/>`_, and is being maintained by
the `Pylons Project <http://www.pylonsproject.org/>`_.
<http://pythonpaste.org/>`_. It is under active development on `GitHub
<https://github.com/pylons/webob>`_. It was originally written by `Ian Bicking
<http://www.ianbicking.org/>`_, and is maintained by the `Pylons Project
<http://www.pylonsproject.org/>`_.
If you've got questions that aren't answered by this documentation, contact the
`Pylons-discuss maillist <http://groups.google.com/group/pylons-discuss>`_ or
join the `#pyramid IRC channel <irc://irc.freenode.net/#pyramid>`_.
WebOb is released under an :doc:`MIT-style license <license>`.
You can clone the source code with:
WebOb development happens on `GitHub <https://github.com/Pylons/webob>`_.
Development version is installable via `easy_install
webob==dev <https://github.com/Pylons/webob/zipball/master>`__. You
can clone the source code with::
.. code-block:: bash
$ git clone https://github.com/Pylons/webob.git
Report issues on the `issue tracker <https://github.com/Pylons/webob/issues>`_.
If you've got questions that aren't answered by this documentation, contact the
`pylons-discuss mail list
<https://groups.google.com/forum/#!forum/pylons-discuss>`_ or join the
`#pyramid IRC channel <https://webchat.freenode.net/?channels=pyramid>`_.
WebOb is released under an :doc:`MIT-style license <license>`.
View
@@ -21,6 +21,8 @@ This is a somewhat different approach to reference documentation compared to
the extracted documentation for the :py:mod:`~webob.request` and
:py:mod:`~webob.response`.
.. _request-reference:
Request
=======
@@ -542,6 +544,8 @@ in ``environ['webob.adhoc_attrs']`` (a dictionary).
>>> req.environ['webob.adhoc_attrs']
{'some_attr': 'blah blah blah'}
.. _response-reference:
Response
========
@@ -598,10 +602,10 @@ You can set any of these attributes, e.g.:
>>> res.charset = 'utf8'
>>> res.text = u"test"
>>> res.body
'test'
b'test'
You can set any attribute with the constructor, like
``Response(charset='utf8')``
``Response(charset='UTF-8')``
Headers
-------
View
@@ -0,0 +1,81 @@
What's New in WebOb 1.6
=======================
Compatibility
~~~~~~~~~~~~~
- Python 3.2 is no longer a supported platform by WebOb
Security
~~~~~~~~
- exc._HTTPMove and any subclasses will now raise a ValueError if the location
field contains a line feed or carriage return. These values may lead to
possible HTTP Response Splitting. The header_getter descriptor has also been
modified to no longer accept headers with a line feed or carriage return.
WebOb does not protect against all possible ways of injecting line feeds or
carriage returns into headers, and should only be thought of as a single line
of defense. Any user input should be sanitized.
See https://github.com/Pylons/webob/pull/229 and
https://github.com/Pylons/webob/issues/217 for more information.
Features
~~~~~~~~
- When WebOb sends an HTTP Exception it will now lazily escape the keys in the
environment, so that only those keys that are actually used in the HTTP
exception are escaped. This solves the problem of keys that are not
serializable as a string in the environment. See
https://github.com/Pylons/webob/pull/139 for more information.
- MIMEAccept now accepts comparisons against wildcards, this allows one to
match on just the media type or sub-type.
Example:
.. code-block:: pycon
>>> accept = MIMEAccept('text/html')
>>> 'text/*' in accept
True
>>> '*/html' in accept
True
>>> '*' in accept
True
- WebOb uses the user agent's Accept header to change what type of information
is returned to the client. This allows the HTTP Exception to return either
HTML, text, or a JSON response. This allows WebOb HTTP Exceptions to be used
in applications where the client is expecting a JSON response. See
https://github.com/Pylons/webob/pull/230 and
https://github.com/Pylons/webob/issues/209 for more information.
Bugfixes
~~~~~~~~
- Response.from_file now parses the status line correctly when the status line
contains an HTTP with version, as well as a status text that contains
multiple white spaces (e.g HTTP/1.1 404 Not Found). See
https://github.com/Pylons/webob/issues/250
- Request.decode would attempt to read from an already consumed stream, it is
now reading from the correct stream. See
https://github.com/Pylons/webob/pull/183 for more information.
- The application/json media type does not allow for a charset as discovery of
the encoding is done at the JSON layer, and it must always be UTF-{8,16,32}.
See the IANA specification at
https://www.iana.org/assignments/media-types/application/json, which notes
No "charset" parameter is defined for this registration.
Adding one really has no effect on compliant recipients.
RFC4627 describes the method for encoding discovery using the JSON content
itself. Upon initialization of a Response WebOb will no longer add a charset
if the content-type is set to JSON. See
https://github.com/Pylons/webob/pull/197,
https://github.com/Pylons/webob/issues/237 and
https://github.com/Pylons/pyramid/issues/1611
View
@@ -12,8 +12,9 @@
README = CHANGES = ''
testing_extras = [
'nose',
'pytest',
'coverage',
'pytest-cov',
]
docs_extras = [
@@ -23,19 +24,17 @@
setup(
name='WebOb',
version='1.6.0a0',
version='1.7.0dev0',
description="WSGI request and response object",
long_description=README + '\n\n' + CHANGES,
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Development Status :: 6 - Mature",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Topic :: Internet :: WWW/HTTP :: WSGI",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
@@ -50,9 +49,7 @@
license='MIT',
packages=['webob'],
zip_safe=True,
test_suite='nose.collector',
tests_require=['nose'],
extras_require = {
extras_require={
'testing': testing_extras,
'docs': docs_extras,
},
View
@@ -1,3 +1,5 @@
import pytest
from webob.request import Request
from webob.acceptparse import Accept
from webob.acceptparse import MIMEAccept
@@ -7,8 +9,6 @@
from webob.acceptparse import AcceptLanguage
from webob.acceptparse import AcceptCharset
from nose.tools import eq_, assert_raises
def test_parse_accept_badq():
assert list(Accept.parse("value1; q=0.1.2")) == [('value1', 1)]
@@ -139,7 +139,8 @@ def test_best_match():
'text/html']) == 'text/html'
assert accept.best_match([('foo/bar', 0.5),
('text/html', 0.4)]) == 'foo/bar'
assert_raises(ValueError, accept.best_match, ['text/*'])
with pytest.raises(ValueError):
accept.best_match(['text/*'])
def test_best_match_with_one_lower_q():
accept = Accept('text/html, foo/bar;q=0.5')
@@ -185,9 +186,7 @@ def test_accept_match_lang():
def test_nil():
nilaccept = NilAccept()
eq_(repr(nilaccept),
"<NilAccept: <class 'webob.acceptparse.Accept'>>"
)
assert repr(nilaccept) == "<NilAccept: <class 'webob.acceptparse.Accept'>>"
assert not nilaccept
assert str(nilaccept) == ''
assert nilaccept.quality('dummy') == 0
@@ -271,7 +270,69 @@ def test_match():
assert mimeaccept._match('image/*', 'image/jpg')
assert mimeaccept._match('*/*', 'image/jpg')
assert not mimeaccept._match('text/html', 'image/jpg')
assert_raises(ValueError, mimeaccept._match, 'image/jpg', '*/*')
mismatches = [
('B/b', 'A/a'),
('B/b', 'B/a'),
('B/b', 'A/b'),
('A/a', 'B/b'),
('B/a', 'B/b'),
('A/b', 'B/b')
]
for mask, offer in mismatches:
assert not mimeaccept._match(mask, offer)
def test_wildcard_matching():
"""
Wildcard matching forces the match to take place against the type
or subtype of the mask and offer (depending on where the wildcard
matches)
"""
mimeaccept = MIMEAccept('type/subtype')
matches = [
('*/*', '*/*'),
('*/*', 'A/*'),
('*/*', '*/a'),
('*/*', 'A/a'),
('A/*', '*/*'),
('A/*', 'A/*'),
('A/*', '*/a'),
('A/*', 'A/a'),
('*/a', '*/*'),
('*/a', 'A/*'),
('*/a', '*/a'),
('*/a', 'A/a'),
('A/a', '*/*'),
('A/a', 'A/*'),
('A/a', '*/a'),
('A/a', 'A/a'),
# Offers might not contain a subtype
('*/*', '*'),
('A/*', '*'),
('*/a', '*')]
for mask, offer in matches:
assert mimeaccept._match(mask, offer)
# Test malformed mask and offer variants where either is missing
# a type or subtype
assert mimeaccept._match('A', offer)
assert mimeaccept._match(mask, 'a')
mismatches = [
('B/b', 'A/*'),
('B/*', 'A/a'),
('B/*', 'A/*'),
('*/b', '*/a')]
for mask, offer in mismatches:
assert not mimeaccept._match(mask, offer)
def test_mimeaccept_contains():
mimeaccept = MIMEAccept('A/a, B/b, C/c')
assert 'A/a' in mimeaccept
assert 'A/*' in mimeaccept
assert '*/a' in mimeaccept
assert not 'A/b' in mimeaccept
assert not 'B/a' in mimeaccept
def test_accept_json():
mimeaccept = MIMEAccept('text/html, *; q=.2, */*; q=.2')
@@ -303,28 +364,28 @@ def test_accept_property_fget():
desc = accept_property('Accept-Charset', '14.2')
req = Request.blank('/', environ={'envkey': 'envval'})
desc.fset(req, 'val')
eq_(desc.fget(req).header_value, 'val')
assert desc.fget(req).header_value == 'val'
def test_accept_property_fget_nil():
desc = accept_property('Accept-Charset', '14.2')
req = Request.blank('/')
eq_(type(desc.fget(req)), NilAccept)
assert type(desc.fget(req)) == NilAccept
def test_accept_property_fset():
desc = accept_property('Accept-Charset', '14.2')
req = Request.blank('/', environ={'envkey': 'envval'})
desc.fset(req, 'baz')
eq_(desc.fget(req).header_value, 'baz')
assert desc.fget(req).header_value == 'baz'
def test_accept_property_fset_acceptclass():
req = Request.blank('/', environ={'envkey': 'envval'})
req.accept_charset = ['utf-8', 'latin-11']
eq_(req.accept_charset.header_value, 'utf-8, latin-11, iso-8859-1')
assert req.accept_charset.header_value == 'utf-8, latin-11, iso-8859-1'
def test_accept_property_fdel():
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)
eq_(type(desc.fget(req)), NilAccept)
assert type(desc.fget(req)) == NilAccept
View
@@ -1,9 +1,9 @@
import pytest
from webob.byterange import Range
from webob.byterange import ContentRange
from webob.byterange import _is_content_range_valid
from nose.tools import assert_true, assert_false, eq_, assert_raises
# Range class
def test_not_satisfiable():
@@ -23,78 +23,80 @@ def test_range_parse():
def test_range_content_range_length_none():
range = Range(0, 100)
eq_(range.content_range(None), None)
assert range.content_range(None) is None
assert isinstance(range.content_range(1), ContentRange)
eq_(tuple(range.content_range(1)), (0,1,1))
eq_(tuple(range.content_range(200)), (0,100,200))
assert tuple(range.content_range(1)) == (0, 1, 1)
assert tuple(range.content_range(200)) == (0, 100, 200)
def test_range_for_length_end_is_none():
# End is None
range = Range(0, None)
eq_(range.range_for_length(100), (0,100))
assert 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)
eq_(range.range_for_length(100), (95,100))
assert range.range_for_length(100) == (95, 100)
def test_range_start_none():
# Start is None
range = Range(None, 99)
eq_(range.range_for_length(100), None)
assert range.range_for_length(100) is None
def test_range_str_end_none():
range = Range(0, None)
eq_(str(range), 'bytes=0-')
assert str(range) == 'bytes=0-'
def test_range_str_end_none_negative_start():
range = Range(-5, None)
eq_(str(range), 'bytes=-5')
assert str(range) == 'bytes=-5'
def test_range_str_1():
range = Range(0, 100)
eq_(str(range), 'bytes=0-99')
assert str(range) == 'bytes=0-99'
def test_range_repr():
range = Range(0, 99)
assert_true(range.__repr__(), '<Range bytes 0-98>')
assert repr(range) == '<Range bytes 0-99>'
# ContentRange class
def test_contentrange_bad_input():
assert_raises(ValueError, ContentRange, None, 99, None)
with pytest.raises(ValueError):
ContentRange(None, 99, None)
def test_contentrange_repr():
contentrange = ContentRange(0, 99, 100)
assert_true(repr(contentrange), '<ContentRange bytes 0-98/100>')
assert repr(contentrange) == '<ContentRange bytes 0-98/100>'
def test_contentrange_str():
contentrange = ContentRange(0, 99, None)
eq_(str(contentrange), 'bytes 0-98/*')
assert str(contentrange) == 'bytes 0-98/*'
contentrange = ContentRange(None, None, 100)
eq_(str(contentrange), 'bytes */100')
assert str(contentrange) == 'bytes */100'
def test_contentrange_iter():
contentrange = ContentRange(0, 99, 100)
assert_true(type(contentrange.__iter__()), iter)
assert_true(ContentRange.parse('bytes 0-99/100').__class__, ContentRange)
eq_(ContentRange.parse(None), None)
eq_(ContentRange.parse('0-99 100'), None)
eq_(ContentRange.parse('bytes 0-99 100'), None)
eq_(ContentRange.parse('bytes 0-99/xxx'), None)
eq_(ContentRange.parse('bytes 0 99/100'), None)
eq_(ContentRange.parse('bytes */100').__class__, ContentRange)
eq_(ContentRange.parse('bytes A-99/100'), None)
eq_(ContentRange.parse('bytes 0-B/100'), None)
eq_(ContentRange.parse('bytes 99-0/100'), None)
eq_(ContentRange.parse('bytes 0 99/*'), None)
import collections
assert isinstance(contentrange, collections.Iterable)
assert ContentRange.parse('bytes 0-99/100').__class__ == ContentRange
assert ContentRange.parse(None) is None
assert ContentRange.parse('0-99 100') is None
assert ContentRange.parse('bytes 0-99 100') is None
assert ContentRange.parse('bytes 0-99/xxx') is None
assert ContentRange.parse('bytes 0 99/100') is None
assert ContentRange.parse('bytes */100').__class__ == ContentRange
assert ContentRange.parse('bytes A-99/100') is None
assert ContentRange.parse('bytes 0-B/100') is None
assert ContentRange.parse('bytes 99-0/100') is None
assert ContentRange.parse('bytes 0 99/*') is None
# _is_content_range_valid function
def test_is_content_range_valid():
assert not _is_content_range_valid( None, 99, 90)
assert not _is_content_range_valid( 99, None, 90)
assert not _is_content_range_valid(None, 99, 90)
assert not _is_content_range_valid(99, None, 90)
assert _is_content_range_valid(None, None, 90)
assert not _is_content_range_valid(None, 99, 90)
assert _is_content_range_valid(0, 99, None)
View
@@ -1,18 +1,14 @@
from nose.tools import eq_
from nose.tools import raises
import unittest
import pytest
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)
assert cc.max_age == -1
class TestUpdateDict(unittest.TestCase):
def setUp(self):
class TestUpdateDict(object):
def setup_method(self, method):
self.call_queue = []
def callback(args):
self.call_queue.append("Called with: %s" % repr(args))
@@ -33,7 +29,7 @@ def test_clear(self):
def test_update(self):
newone = self.make_one(self.callback)
d = {'one' : 1 }
d = {'one': 1}
newone.update(d)
assert newone == d
@@ -51,7 +47,7 @@ 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]
assert self.call_queue[-1] == "Called with: {'haters': 'gonna-hate'}"
# no effect if failobj is not set
assert newone.setdefault('haters', 'gonna-love') == 'gonna-hate'
@@ -71,12 +67,8 @@ def test_popitem(self):
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):
class TestExistProp(object):
"""
Test webob.cachecontrol.exists_property
"""
@@ -104,10 +96,10 @@ 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
with pytest.raises(AttributeError):
obj = self.make_one()()
obj.badprop = True
def test_set_w_value(self):
obj = self.make_one()()
@@ -118,10 +110,10 @@ def test_set_w_value(self):
def test_del_value(self):
obj = self.make_one()()
del obj.prop
assert not 'prop' in obj.properties
assert 'prop' not in obj.properties
class TestValueProp(unittest.TestCase):
class TestValueProp(object):
"""
Test webob.cachecontrol.exists_property
"""
@@ -148,7 +140,6 @@ def test_get_on_class(self):
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()()
@@ -171,35 +162,34 @@ class Dummy(object):
dummy = Dummy()
def assign():
dummy.prop = 'foo'
self.assertRaises(AttributeError, assign)
with pytest.raises(AttributeError):
assign()
def test_set_type_true(self):
dummy = self.make_one()()
dummy.prop = True
self.assertEqual(dummy.prop, None)
assert dummy.prop is 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
assert dummy.prop == "dummy"
# TODO: 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
assert dummy.prop == "dummy"
def test_copy_cc():
from webob.cachecontrol import CacheControl
cc = CacheControl({'header':'%', "msg":'arewerichyet?'}, 'request')
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())
@@ -212,51 +202,52 @@ def test_serialize_cache_control_cache_control_object():
def test_serialize_cache_control_object_with_headers():
from webob.cachecontrol import serialize_cache_control, CacheControl
result = serialize_cache_control(CacheControl({'header':'a'}, 'request'))
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'))
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'))
result = serialize_cache_control(CacheControl({'header': '""'}, 'request'))
assert result == 'header=""""'
class TestCacheControl(unittest.TestCase):
class TestCacheControl(object):
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.assertEqual(cc.properties, {'a':1})
self.assertEqual(cc.type, 'typ')
cc = self.make_one({'a': 1}, 'typ')
assert cc.properties == {'a': 1}
assert cc.type == 'typ'
def test_parse(self):
from webob.cachecontrol import CacheControl
cc = CacheControl.parse("public, max-age=315360000")
self.assertEqual(type(cc), CacheControl)
self.assertEqual(cc.max_age, 315360000)
self.assertEqual(cc.public, True)
assert type(cc) == CacheControl
assert cc.max_age == 315360000
assert cc.public is True
def test_parse_updates_to(self):
from webob.cachecontrol import CacheControl
def foo(arg): return { 'a' : 1 }
def foo(arg):
return {'a': 1}
cc = CacheControl.parse("public, max-age=315360000", updates_to=foo)
self.assertEqual(type(cc), CacheControl)
self.assertEqual(cc.max_age, 315360000)
assert type(cc) == CacheControl
assert cc.max_age == 315360000
def test_parse_valueerror_int(self):
from webob.cachecontrol import CacheControl
def foo(arg): return { 'a' : 1 }
def foo(arg):
return {'a': 1}
cc = CacheControl.parse("public, max-age=abc")
self.assertEqual(type(cc), CacheControl)
self.assertEqual(cc.max_age, 'abc')
assert type(cc) == CacheControl
assert cc.max_age == 'abc'
def test_repr(self):
cc = self.make_one({'a':'1'}, 'typ')
result = repr(cc)
self.assertEqual(result, "<CacheControl 'a=1'>")
cc = self.make_one({'a': '1'}, 'typ')
assert repr(cc) == "<CacheControl 'a=1'>"
View
@@ -1,10 +1,11 @@
import time
import urllib
import pytest
from webob import Request, Response
from webob.dec import wsgify
from webob.client import SendRequest
from .test_in_wsgiref import serve
from nose.tools import assert_raises
@wsgify
@@ -44,7 +45,8 @@ def test_client(client_app=None):
assert req.environ.get('SERVER_NAME') is None
assert req.environ.get('SERVER_PORT') is None
assert req.environ.get('HTTP_HOST') is None
assert_raises(ValueError, req.send, client_app)
with pytest.raises(ValueError):
req.send(client_app)
req = Request.blank(server.url)
req.environ['CONTENT_LENGTH'] = 'not a number'
assert req.send(client_app).status_code == 200
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,14 +1,7 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
from webob import cookies
from webob.compat import text_
from nose.tools import (eq_, assert_raises)
import unittest
from webob.compat import native_
from webob.compat import PY3
import warnings
from webob import cookies
def setup_module(module):
cookies._should_raise = False
@@ -19,10 +12,10 @@ def test_invalid_cookie_space():
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Trigger a warning.
cookies._value_quote(b'hello world')
eq_(len(w), 1)
eq_(issubclass(w[-1].category, RuntimeWarning), True)
eq_("ValueError" in str(w[-1].message), True)
assert len(w) == 1
assert issubclass(w[-1].category, RuntimeWarning) is True
assert "ValueError" in str(w[-1].message)
View
@@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
import pytest
import datetime
import calendar
from email.utils 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')
assert x.tzname(datetime.datetime.now()) == 'UTC'
assert x.dst(datetime.datetime.now()) == datetime.timedelta(0)
assert x.utcoffset(datetime.datetime.now()) == datetime.timedelta(0)
assert repr(x) == 'UTC'
def test_parse_date():
"""Testing datetime_utils.parse_date.
@@ -21,30 +20,34 @@ def test_parse_date():
* a submitted value that cannot be parse into a date
* a valid RFC2822 date with and without timezone
"""
ret = datetime_utils.parse_date(None)
ok_(ret is None, "We passed a None value "
"to parse_date. We should get None but instead we got %s" %\
ret)
assert ret is None, ("We passed a None value to parse_date. We should get"
" None but instead we got %s" % ret)
ret = datetime_utils.parse_date('Hi There')
ok_(ret is None, "We passed an invalid value "
"to parse_date. We should get None but instead we got %s" %\
ret)
assert ret is None, ("We passed an invalid value to parse_date. We should"
" get None but instead we got %s" % ret)
ret = datetime_utils.parse_date(1)
ok_(ret is None, "We passed an invalid value "
"to parse_date. We should get None but instead we got %s" %\
ret)
ret = datetime_utils.parse_date('á')
ok_(ret is None, "We passed an invalid value "
"to parse_date. We should get None but instead we got %s" %\
ret)
assert ret is None, ("We passed an invalid value to parse_date. We should"
" get None but instead we got %s" % ret)
ret = datetime_utils.parse_date('\xc3')
assert ret is None, ("We passed an invalid value 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.datetime(
1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
assert 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.datetime(1995, 11, 20, 19, 12, 8, tzinfo=datetime_utils.UTC))
assert ret == datetime.datetime(
1995, 11, 20, 19, 12, 8, tzinfo=datetime_utils.UTC)
ret = datetime_utils.parse_date(Uncooperative())
eq_(ret, None)
assert ret is None
class Uncooperative(object):
def __str__(self):
@@ -61,16 +64,17 @@ def test_serialize_date():
from webob.compat import text_
ret = datetime_utils.serialize_date('Mon, 20 Nov 1995 19:12:08 GMT')
assert isinstance(ret, str)
eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
assert ret == 'Mon, 20 Nov 1995 19:12:08 GMT'
ret = datetime_utils.serialize_date(text_('Mon, 20 Nov 1995 19:12:08 GMT'))
assert isinstance(ret, str)
eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
assert ret == 'Mon, 20 Nov 1995 19:12:08 GMT'
dt = formatdate(
calendar.timegm(
(datetime.datetime.now()+datetime.timedelta(1)).timetuple()),
(datetime.datetime.now() + datetime.timedelta(1)).timetuple()),
usegmt=True)
eq_(dt, datetime_utils.serialize_date(datetime.timedelta(1)))
assert_raises(ValueError, datetime_utils.serialize_date, None)
assert dt == datetime_utils.serialize_date(datetime.timedelta(1))
with pytest.raises(ValueError):
datetime_utils.serialize_date(None)
def test_parse_date_delta():
"""Testing datetime_utils.parse_date_delta
@@ -79,38 +83,32 @@ def test_parse_date_delta():
* passing a value that fails the conversion to int, should call
parse_date
"""
ok_(datetime_utils.parse_date_delta(None) is None, 'Passing none value, '
'should return None')
assert 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.datetime(
1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
assert 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:
with _NowRestorer(WHEN):
ret = datetime_utils.parse_date_delta(1)
eq_(ret, WHEN + datetime.timedelta(0, 1))
finally:
nr.__exit__(None, None, None)
assert ret == WHEN + datetime.timedelta(0, 1)
def test_serialize_date_delta():
"""Testing datetime_utils.serialize_date_delta
We need to verify the following scenarios:
* 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')
assert datetime_utils.serialize_date_delta(1) == '1'
assert datetime_utils.serialize_date_delta(1.5) == '1'
ret = datetime_utils.serialize_date_delta('Mon, 20 Nov 1995 19:12:08 GMT')
assert type(ret) is (str)
eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
assert ret == 'Mon, 20 Nov 1995 19:12:08 GMT'
def test_timedelta_to_seconds():
val = datetime.timedelta(86400)
result = datetime_utils.timedelta_to_seconds(val)
eq_(result, 7464960000)
assert result == 7464960000
class _NowRestorer(object):
View
@@ -4,7 +4,6 @@
from webob.dec import wsgify
from webob.compat import bytes_
from webob.compat import text_
from webob.compat import PY3
class DecoratorTests(unittest.TestCase):
def _testit(self, app, req):
@@ -170,6 +169,22 @@ def show_vars(req):
self.assertEqual(resp.charset, 'UTF-8')
self.assertEqual(resp.content_length, 40)
def test_middleware_as_decorator(self):
resp_str = "These are the vars: %s"
@wsgify.middleware
def set_urlvar(req, app, **vars):
req.urlvars.update(vars)
return app(req)
@set_urlvar(a=1,b=2)
@wsgify
def show_vars(req):
return resp_str % (sorted(req.urlvars.items()))
resp = self._testit(show_vars, '/path')
self.assertEqual(resp.body, bytes_(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):
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,9 +1,8 @@
import unittest
from webob import Response
from webob.etag import ETagMatcher, IfRange, etag_property, ETagMatcher
import pytest
from webob.etag import ETagMatcher, IfRange, etag_property
class etag_propertyTests(unittest.TestCase):
class Test_etag_properties(object):
def _makeDummyRequest(self, **kw):
"""
Return a DummyRequest object with attrs from kwargs.
@@ -19,44 +18,44 @@ def __init__(self, **kwargs):
def test_fget_missing_key(self):
ep = etag_property("KEY", "DEFAULT", "RFC_SECTION")
req = self._makeDummyRequest(environ={})
self.assertEqual(ep.fget(req), "DEFAULT")
assert ep.fget(req) == "DEFAULT"
def test_fget_found_key(self):
ep = etag_property("KEY", "DEFAULT", "RFC_SECTION")
req = self._makeDummyRequest(environ={'KEY':'"VALUE"'})
req = self._makeDummyRequest(environ={'KEY': '"VALUE"'})
res = ep.fget(req)
self.assertEqual(res.etags, ['VALUE'])
assert res.etags == ['VALUE']
def test_fget_star_key(self):
ep = etag_property("KEY", "DEFAULT", "RFC_SECTION")
req = self._makeDummyRequest(environ={'KEY':'*'})
req = self._makeDummyRequest(environ={'KEY': '*'})
res = ep.fget(req)
import webob.etag
self.assertEqual(type(res), webob.etag._AnyETag)
self.assertEqual(res.__dict__, {})
assert type(res) == webob.etag._AnyETag
assert res.__dict__ == {}
def test_fset_None(self):
ep = etag_property("KEY", "DEFAULT", "RFC_SECTION")
req = self._makeDummyRequest(environ={'KEY':'*'})
req = self._makeDummyRequest(environ={'KEY': '*'})
res = ep.fset(req, None)
self.assertEqual(res, None)
assert res is None
def test_fset_not_None(self):
ep = etag_property("KEY", "DEFAULT", "RFC_SECTION")
req = self._makeDummyRequest(environ={'KEY':'OLDVAL'})
req = self._makeDummyRequest(environ={'KEY': 'OLDVAL'})
res = ep.fset(req, "NEWVAL")
self.assertEqual(res, None)
self.assertEqual(req.environ['KEY'], 'NEWVAL')
assert res is None
assert req.environ['KEY'] == 'NEWVAL'
def test_fedl(self):
ep = etag_property("KEY", "DEFAULT", "RFC_SECTION")
req = self._makeDummyRequest(environ={'KEY':'VAL', 'QUAY':'VALYOU'})
req = self._makeDummyRequest(environ={'KEY': 'VAL', 'QUAY': 'VALYOU'})
res = ep.fdel(req)
self.assertEqual(res, None)
self.assertFalse('KEY' in req.environ)
self.assertEqual(req.environ['QUAY'], 'VALYOU')
assert res is None
assert 'KEY' not in req.environ
assert req.environ['QUAY'] == 'VALYOU'
class AnyETagTests(unittest.TestCase):
class Test_AnyETag(object):
def _getTargetClass(self):
from webob.etag import _AnyETag
return _AnyETag
@@ -66,25 +65,26 @@ def _makeOne(self, *args, **kw):
def test___repr__(self):
etag = self._makeOne()
self.assertEqual(etag.__repr__(), '<ETag *>')
assert etag.__repr__() == '<ETag *>'
def test___nonzero__(self):
etag = self._makeOne()
self.assertEqual(etag.__nonzero__(), False)
assert etag.__nonzero__() is False
def test___contains__something(self):
etag = self._makeOne()
self.assertEqual('anything' in etag, True)
assert 'anything' in etag
def test_weak_match_something(self):
etag = self._makeOne()
self.assertRaises(DeprecationWarning, etag.weak_match, 'anything')
with pytest.raises(DeprecationWarning):
etag.weak_match('anything')
def test___str__(self):
etag = self._makeOne()
self.assertEqual(str(etag), '*')
assert str(etag) == '*'
class NoETagTests(unittest.TestCase):
class Test_NoETag(object):
def _getTargetClass(self):
from webob.etag import _NoETag
return _NoETag
@@ -94,78 +94,74 @@ def _makeOne(self, *args, **kw):
def test___repr__(self):
etag = self._makeOne()
self.assertEqual(etag.__repr__(), '<No ETag>')
assert etag.__repr__() == '<No ETag>'
def test___nonzero__(self):
etag = self._makeOne()
self.assertEqual(etag.__nonzero__(), False)
assert etag.__nonzero__() is False
def test___contains__something(self):
etag = self._makeOne()
assert 'anything' not in etag
def test___str__(self):
etag = self._makeOne()
self.assertEqual(str(etag), '')
assert str(etag) == ''
class ParseTests(unittest.TestCase):
class Test_Parse(object):
def test_parse_None(self):
et = ETagMatcher.parse(None)
self.assertEqual(et.etags, [])
assert et.etags == []
def test_parse_anyetag(self):
# these tests smell bad, are they useful?
et = ETagMatcher.parse('*')
self.assertEqual(et.__dict__, {})
self.assertEqual(et.__repr__(), '<ETag *>')
assert et.__dict__ == {}
assert et.__repr__() == '<ETag *>'
def test_parse_one(self):
et = ETagMatcher.parse('"ONE"')
self.assertEqual(et.etags, ['ONE'])
assert et.etags == ['ONE']
def test_parse_invalid(self):
for tag in ['one', 'one, two', '"one two']:
et = ETagMatcher.parse(tag)
self.assertEqual(et.etags, [tag])
assert et.etags == [tag]
et = ETagMatcher.parse('"foo" and w/"weak"', strong=False)
self.assertEqual(et.etags, ['foo'])
assert et.etags == ['foo']
def test_parse_commasep(self):
et = ETagMatcher.parse('"ONE", "TWO"')
self.assertEqual(et.etags, ['ONE', 'TWO'])
assert et.etags, ['ONE' == 'TWO']
def test_parse_commasep_w_weak(self):
et = ETagMatcher.parse('"ONE", W/"TWO"')
self.assertEqual(et.etags, ['ONE'])
assert et.etags == ['ONE']
et = ETagMatcher.parse('"ONE", W/"TWO"', strong=False)
self.assertEqual(et.etags, ['ONE', 'TWO'])
assert et.etags, ['ONE' == 'TWO']
def test_parse_quoted(self):
et = ETagMatcher.parse('"ONE"')
self.assertEqual(et.etags, ['ONE'])
assert et.etags == ['ONE']
def test_parse_quoted_two(self):
et = ETagMatcher.parse('"ONE", "TWO"')
self.assertEqual(et.etags, ['ONE', 'TWO'])
assert et.etags, ['ONE' == 'TWO']
def test_parse_quoted_two_weak(self):
et = ETagMatcher.parse('"ONE", W/"TWO"')
self.assertEqual(et.etags, ['ONE'])
assert et.etags == ['ONE']
et = ETagMatcher.parse('"ONE", W/"TWO"', strong=False)
self.assertEqual(et.etags, ['ONE', 'TWO'])
assert et.etags, ['ONE' == 'TWO']
class IfRangeTests(unittest.TestCase):
class Test_IfRange(object):
def test___repr__(self):
self.assertEqual(repr(IfRange(None)), 'IfRange(None)')
assert repr(IfRange(None)) == 'IfRange(None)'
def test___repr__etag(self):
self.assertEqual(repr(IfRange('ETAG')), "IfRange('ETAG')")
assert repr(IfRange('ETAG')) == "IfRange('ETAG')"
def test___repr__date(self):
ir = IfRange.parse('Fri, 09 Nov 2001 01:08:47 GMT')
self.assertEqual(
repr(ir),
'IfRangeDate(datetime.datetime(2001, 11, 9, 1, 8, 47, tzinfo=UTC))'
)
assert repr(ir) == 'IfRangeDate(datetime.datetime(2001, 11, 9, 1, 8, 47, tzinfo=UTC))'
View
@@ -1,10 +1,11 @@
import pytest
from webob.etag import IfRange, ETagMatcher
from webob import Response
from nose.tools import eq_, assert_raises
def test_if_range_None():
ir = IfRange.parse(None)
eq_(str(ir), '')
assert str(ir) == ''
assert not ir
assert Response() in ir
assert Response(etag='foo') in ir
@@ -13,7 +14,7 @@ def test_if_range_None():
def test_if_range_match_date():
date = 'Fri, 09 Nov 2001 01:08:47 GMT'
ir = IfRange.parse(date)
eq_(str(ir), date)
assert str(ir) == date
assert Response() not in ir
assert Response(etag='etag') not in ir
assert Response(etag=date) not in ir
@@ -22,27 +23,27 @@ def test_if_range_match_date():
def test_if_range_match_etag():
ir = IfRange.parse('ETAG')
eq_(str(ir), '"ETAG"')
assert str(ir) == '"ETAG"'
assert Response() not in ir
assert Response(etag='other') not in ir
assert Response(etag='ETAG') in ir
assert Response(etag='W/"ETAG"') not in ir
def test_if_range_match_etag_weak():
ir = IfRange.parse('W/"ETAG"')
eq_(str(ir), '')
assert str(ir) == ''
assert Response(etag='ETAG') not in ir
assert Response(etag='W/"ETAG"') not in ir
def test_if_range_repr():
eq_(repr(IfRange.parse(None)), 'IfRange(<ETag *>)')
eq_(str(IfRange.parse(None)), '')
assert repr(IfRange.parse(None)) == 'IfRange(<ETag *>)'
assert str(IfRange.parse(None)) == ''
def test_resp_etag():
def t(tag, res, raw, strong):
eq_(Response(etag=tag).etag, res)
eq_(Response(etag=tag).headers.get('etag'), raw)
eq_(Response(etag=tag).etag_strong, strong)
assert Response(etag=tag).etag == res
assert Response(etag=tag).headers.get('etag') == raw
assert Response(etag=tag).etag_strong == strong
t('foo', 'foo', '"foo"', 'foo')
t('"foo"', 'foo', '"foo"', 'foo')
t('a"b', 'a"b', '"a\\"b"', 'a"b')
@@ -57,14 +58,15 @@ def t(tag, res, raw, strong):
def test_matcher():
matcher = ETagMatcher(['ETAGS'])
matcher = ETagMatcher(['ETAGS'])
eq_(matcher.etags, ['ETAGS'])
assert_raises(DeprecationWarning, matcher.weak_match, "etag")
assert matcher.etags == ['ETAGS']
with pytest.raises(DeprecationWarning):
matcher.weak_match("etag")
assert "ETAGS" in matcher
assert "WEAK" not in matcher
assert "BEER" not in matcher
assert None not in matcher
eq_(repr(matcher), '<ETag ETAGS>')
eq_(str(matcher), '"ETAGS"')
assert repr(matcher) == '<ETag ETAGS>'
assert str(matcher) == '"ETAGS"'
matcher2 = ETagMatcher(("ETAG1","ETAG2"))
eq_(repr(matcher2), '<ETag ETAG1 or ETAG2>')
assert repr(matcher2) == '<ETag ETAG1 or ETAG2>'
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,22 +1,20 @@
# -*- coding: utf-8 -*-
from webob import headers
from nose.tools import ok_, assert_raises, eq_
import pytest
class TestError(Exception):
pass
from webob import headers
def test_ResponseHeaders_delitem_notpresent():
"""Deleting a missing key from ResponseHeaders should raise a KeyError"""
d = headers.ResponseHeaders()
assert_raises(KeyError, d.__delitem__, 'b')
with pytest.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)
assert 'a' not in d
def test_ResponseHeaders_setdefault():
"""Testing set_default for ResponseHeaders"""
@@ -32,82 +30,86 @@ 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')
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')
with pytest.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
with pytest.raises(KeyError):
d.pop('z')
def test_ResponseHeaders_getitem_miss():
d = headers.ResponseHeaders()
assert_raises(KeyError, d.__getitem__, 'a')
with pytest.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])
assert 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})
assert 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])
assert result == [1]
def test_ResponseHeaders_contains():
d = headers.ResponseHeaders()
d['a'] = 1
ok_('a' in d)
ok_(not 'b' in d)
assert 'a' in d
assert 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')
with pytest.raises(KeyError):
d.__delitem__('CONTENT-LENGTH')
def test_EnvironHeaders_getitem():
d = headers.EnvironHeaders({'CONTENT_LENGTH': '10'})
eq_(d['CONTENT-LENGTH'], '10')
assert d['CONTENT-LENGTH'] == '10'
def test_EnvironHeaders_setitem():
d = headers.EnvironHeaders({})
d['abc'] = '10'
eq_(d['abc'], '10')
assert d['abc'] == '10'
def test_EnvironHeaders_contains():
d = headers.EnvironHeaders({})
d['a'] = '10'
ok_('a' in d)
ok_(not 'b' in d)
assert 'a' in d
assert 'b' not in d
def test__trans_key_not_basestring():
result = headers._trans_key(None)
eq_(result, None)
assert result == None
def test__trans_key_not_a_header():
result = headers._trans_key('')
eq_(result, None)
assert result == None
def test__trans_key_key2header():
result = headers._trans_key('CONTENT_TYPE')
eq_(result, 'Content-Type')
assert result == 'Content-Type'
def test__trans_key_httpheader():
result = headers._trans_key('HTTP_FOO_BAR')
eq_(result, 'Foo-Bar')
assert result == 'Foo-Bar'
View
@@ -4,6 +4,14 @@
import random
import socket
import cgi
import pytest
from wsgiref.simple_server import make_server
from wsgiref.simple_server import WSGIRequestHandler
from wsgiref.simple_server import WSGIServer
from wsgiref.simple_server import ServerHandler
from webob.request import Request
from webob.response import Response
from webob.compat import url_open
@@ -12,12 +20,6 @@
from webob.compat import Queue
from webob.compat import Empty
from contextlib import contextmanager
from nose.tools import assert_raises
from nose.tools import eq_ as eq
from wsgiref.simple_server import make_server
from wsgiref.simple_server import WSGIRequestHandler
from wsgiref.simple_server import WSGIServer
from wsgiref.simple_server import ServerHandler
log = logging.getLogger(__name__)
@@ -77,7 +79,8 @@ def _test_app_req_interrupt(env, sr):
'request.content_length is %s instead of %s' % (cl, target_cl))
op = _test_ops_req_interrupt[req.path_info]
log.info("Running test: %s", req.path_info)
assert_raises(IOError, op, req)
with pytest.raises(IOError):
op(req)
except:
_global_res.put(sys.exc_info())
else:
@@ -95,7 +98,7 @@ def _req_int_cgi(req):
def _req_int_readline(req):
try:
eq(req.body_file.readline(), b'a=b\n')
assert req.body_file.readline() == b'a=b\n'
except IOError:
# too early to detect disconnect
raise AssertionError("False disconnect alert")
View
@@ -1,7 +1,7 @@
import cgi
import pytest
from webob.util import html_escape
from webob.multidict import MultiDict
from nose.tools import eq_ as eq, assert_raises
from webob.compat import (
text_,
PY3
@@ -20,12 +20,12 @@ def test_html_escape():
# The apostrophe is *not* escaped, which some might consider to be
# a serious bug (see, e.g. http://www.cvedetails.com/cve/CVE-2010-2480/)
(text_('the majestic m\xf8ose'), 'the majestic m&#248;ose'),
#("'", "&#39;")
# ("'", "&#39;")
# 8-bit strings are passed through
(text_('\xe9'), '&#233;'),
## (text_(b'the majestic m\xf8ose').encode('utf-8'),
## 'the majestic m\xc3\xb8ose'),
# (text_(b'the majestic m\xf8ose').encode('utf-8'),
# 'the majestic m\xc3\xb8ose'),
# ``None`` is treated specially, and returns the empty string.
(None, ''),
@@ -43,7 +43,7 @@ def test_html_escape():
(t_esc_Unicode(), '&#233;'),
(t_esc_UnsafeAttrs(), '&lt;UnsafeAttrs&gt;'),
]:
eq(html_escape(v), s)
assert html_escape(v) == s
class t_esc_HTML(object):
def __html__(self):
@@ -67,35 +67,33 @@ def __str__(self):
def __unicode__(self):
return text_(b'm\xf8ose')
def test_multidict():
d = MultiDict(a=1, b=2)
eq(d['a'], 1)
eq(d.getall('c'), [])
assert d['a'] == 1
assert d.getall('c') == []
d.add('a', 2)
eq(d['a'], 2)
eq(d.getall('a'), [1, 2])
assert d['a'] == 2
assert d.getall('a') == [1, 2]
d['b'] = 4
eq(d.getall('b'), [4])
eq(list(d.keys()), ['a', 'a', 'b'])
eq(list(d.items()), [('a', 1), ('a', 2), ('b', 4)])
eq(d.mixed(), {'a': [1, 2], 'b': 4})
assert d.getall('b') == [4]
assert list(d.keys()) == ['a', 'a', 'b']
assert list(d.items()) == [('a', 1), ('a', 2), ('b', 4)]
assert d.mixed() == {'a': [1, 2], 'b': 4}
# test getone
# KeyError: "Multiple values match 'a': [1, 2]"
assert_raises(KeyError, d.getone, 'a')
eq(d.getone('b'), 4)
with pytest.raises(KeyError):
d.getone('a')
assert d.getone('b') == 4
# KeyError: "Key not found: 'g'"
assert_raises(KeyError, d.getone, 'g')
with pytest.raises(KeyError):
d.getone('g')
eq(d.dict_of_lists(), {'a': [1, 2], 'b': [4]})
assert d.dict_of_lists() == {'a': [1, 2], 'b': [4]}
assert 'b' in d
assert 'e' not in d
d.clear()
@@ -107,28 +105,29 @@ def test_multidict():
e.clear()
e['f'] = 42
d.update(e)
eq(d, MultiDict([('a', 4), ('a', 5), ('f', 42)]))
assert d == MultiDict([('a', 4), ('a', 5), ('f', 42)])
f = d.pop('a')
eq(f, 4)
eq(d['a'], 5)
assert f == 4
assert d['a'] == 5
eq(d.pop('g', 42), 42)
assert_raises(KeyError, d.pop, 'n')
assert d.pop('g', 42) == 42
with pytest.raises(KeyError):
d.pop('n')
# TypeError: pop expected at most 2 arguments, got 3
assert_raises(TypeError, d.pop, 4, 2, 3)
with pytest.raises(TypeError):
d.pop(4, 2, 3)
d.setdefault('g', []).append(4)
eq(d, MultiDict([('a', 5), ('f', 42), ('g', [4])]))
assert d == MultiDict([('a', 5), ('f', 42), ('g', [4])])
def test_multidict_init():
d = MultiDict([('a', 'b')], c=2)
eq(repr(d), "MultiDict([('a', 'b'), ('c', 2)])")
eq(d, MultiDict([('a', 'b')], c=2))
assert repr(d) == "MultiDict([('a', 'b'), ('c', 2)])"
assert d == MultiDict([('a', 'b')], c=2)
# TypeError: MultiDict can only be called with one positional argument
assert_raises(TypeError, MultiDict, 1, 2, 3)
with pytest.raises(TypeError):
MultiDict(1, 2, 3)
# TypeError: MultiDict.view_list(obj) takes only actual list objects, not None
assert_raises(TypeError, MultiDict.view_list, None)
with pytest.raises(TypeError):
MultiDict.view_list(None)
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,5 +1,6 @@
import pytest
from webob.request import Request
from nose.tools import eq_ as eq, assert_raises
from webob.compat import bytes_
def test_request_no_method():
@@ -43,7 +44,7 @@ def test_request_read_after_setting_body_file():
def test_request_readlines():
req = Request.blank('/', POST='a\n'*3)
req.is_body_seekable = False
eq(req.body_file.readlines(), [b'a\n'] * 3)
assert req.body_file.readlines() == [b'a\n'] * 3
def test_request_delete_with_body():
req = Request.blank('/', method='DELETE')
@@ -75,25 +76,25 @@ def read(self, size=-1):
self.was_read = True
return self.data
def test_limited_length_file_repr():
req = Request.blank('/', POST='x')
req.body_file_raw = 'dummy'
req.is_body_seekable = False
eq(repr(req.body_file.raw), "<LimitedLengthFile('dummy', maxlen=1)>")
assert repr(req.body_file.raw), "<LimitedLengthFile('dummy' == maxlen=1)>"
def test_request_wrong_clen(is_seekable=False):
tlen = 1<<20
req = Request.blank('/', POST='x'*tlen)
eq(req.content_length, tlen)
assert req.content_length == tlen
req.body_file = _Helper_test_request_wrong_clen(req.body_file)
eq(req.content_length, None)
assert req.content_length == None
req.content_length = tlen + 100
req.is_body_seekable = is_seekable
eq(req.content_length, tlen+100)
assert req.content_length == tlen+100
# this raises AssertionError if the body reading
# trusts content_length too much
assert_raises(IOError, req.copy_body)
with pytest.raises(IOError):
req.copy_body()
def test_request_wrong_clen_seekable():
test_request_wrong_clen(is_seekable=True)
@@ -111,7 +112,6 @@ def read(self, *args):
self.file_ended = True
return r
def test_disconnect_detection_cgi():
data = 'abc'*(1<<20)
req = Request.blank('/', POST={'file':('test-file', data)})
@@ -135,32 +135,32 @@ def test_charset_in_content_type():
'QUERY_STRING':'a=b',
'CONTENT_TYPE':'text/html;charset=ascii'
})
eq(req.charset, 'ascii')
eq(dict(req.GET), {'a': 'b'})
eq(dict(req.POST), {})
assert req.charset == 'ascii'
assert dict(req.GET) == {'a': 'b'}
assert dict(req.POST) == {}
req.charset = 'ascii' # no exception
assert_raises(DeprecationWarning, setattr, req, 'charset', 'utf-8')
with pytest.raises(DeprecationWarning):
setattr(req, 'charset', 'utf-8')
# again no exception
req = Request({
'REQUEST_METHOD': 'POST',
'QUERY_STRING':'a=b',
'CONTENT_TYPE':'multipart/form-data;charset=ascii'
})
eq(req.charset, 'ascii')
eq(dict(req.GET), {'a': 'b'})
assert_raises(DeprecationWarning, getattr, req, 'POST')
assert req.charset == 'ascii'
assert dict(req.GET) == {'a': 'b'}
with pytest.raises(DeprecationWarning):
getattr(req, 'POST')
def test_json_body_invalid_json():
request = Request.blank('/', POST=b'{')
assert_raises(ValueError, getattr, request, 'json_body')
with pytest.raises(ValueError):
getattr(request, 'json_body')
def test_json_body_valid_json():
request = Request.blank('/', POST=b'{"a":1}')
eq(request.json_body, {'a':1})
assert request.json_body == {'a':1}
def test_json_body_alternate_charset():
import json
@@ -171,11 +171,12 @@ def test_json_body_alternate_charset():
request = Request.blank('/', POST=body)
request.content_type = 'application/json; charset=utf-16'
s = request.json_body['a']
eq(s.encode('utf8'), b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf')
assert s.encode('utf8') == b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf'
def test_json_body_GET_request():
request = Request.blank('/')
assert_raises(ValueError, getattr, request, 'json_body')
with pytest.raises(ValueError):
getattr(request, 'json_body')
def test_non_ascii_body_params():
body = b'test=%D1%82%D0%B5%D1%81%D1%82'
@@ -184,4 +185,4 @@ def test_non_ascii_body_params():
req.params
# accessing body again makes the POST dict serialize again
# make sure it can handle the non-ascii characters in the query
eq(req.body, body)
assert req.body == body
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,14 +1,6 @@
# coding: cp1251
from webob.request import Request, Transcoder
from webob.response import Response
from webob.compat import text_, native_
from nose.tools import eq_
# def tapp(env, sr):
# req = Request(env)
# r = Response(str(req))
# #r = Response(str(dict(req.POST)))
# return r(env, sr)
t1 = b'--BOUNDARY\r\nContent-Disposition: form-data; name="a"\r\n\r\n\xea\xf3...\r\n--BOUNDARY--'
t2 = b'--BOUNDARY\r\nContent-Disposition: form-data; name="a"; filename="file"\r\n\r\n\xea\xf3...\r\n--BOUNDARY--'
@@ -17,62 +9,50 @@
def test_transcode():
def tapp(env, sr):
req = Request(env)
#import pprint; pprint.pprint(req.environ)
#print(req.body)
req = req.decode()
#import pprint; pprint.pprint(req.environ)
#print(req.body)
v = req.POST[req.query_string]
if hasattr(v, 'filename'):
r = Response(text_('%s\n%r' % (v.filename, v.value)))
else:
r = Response(v)
return r(env, sr)
text = b'\xea\xf3...'.decode('cp1251')
def test(post):
req = Request.blank('/?a', POST=post)
req.environ['CONTENT_TYPE'] = 'multipart/form-data; charset=windows-1251; boundary=BOUNDARY'
return req.get_response(tapp)
r = test(t1)
eq_(r.text, text)
assert r.text == text
r = test(t2)
eq_(r.text, 'file\n%r' % text.encode('cp1251'))
assert r.text == 'file\n%r' % text.encode('cp1251')
r = test(t3)
eq_(r.text, "%s\n%r" % (text, b'foo'))
#req = Request.blank('/?a', POST={'a': ('file', text.encode('cp1251'))},
# req = Request({}, charset='utf8')
# req = Request({})
# print req.charset
# print req._charset_cache
# print req.environ.get('CONTENT_TYPE')
#print '\xd0\xba\xd1\x83...'.decode('utf8').encode('cp1251')
#print u'\u043a'.encode('cp1251')
assert r.text, "%s\n%r" % (text == b'foo')
def test_transcode_query():
req = Request.blank('/?%EF%F0%E8=%E2%E5%F2')
req2 = req.decode('cp1251')
eq_(req2.query_string, '%D0%BF%D1%80%D0%B8=%D0%B2%D0%B5%D1%82')
assert req2.query_string == '%D0%BF%D1%80%D0%B8=%D0%B2%D0%B5%D1%82'
def test_transcode_non_multipart():
req = Request.blank('/?a', POST='%EF%F0%E8=%E2%E5%F2')
req._content_type_raw = 'application/x-www-form-urlencoded'
req2 = req.decode('cp1251')
eq_(native_(req2.body), '%D0%BF%D1%80%D0%B8=%D0%B2%D0%B5%D1%82')
assert native_(req2.body) == '%D0%BF%D1%80%D0%B8=%D0%B2%D0%B5%D1%82'
def test_transcode_non_form():
req = Request.blank('/?a', POST='%EF%F0%E8=%E2%E5%F2')
req._content_type_raw = 'application/x-foo'
req2 = req.decode('cp1251')
eq_(native_(req2.body), '%EF%F0%E8=%E2%E5%F2')
assert native_(req2.body) == '%EF%F0%E8=%E2%E5%F2'
def test_transcode_noop():
req = Request.blank('/')
assert req.decode() is req
def test_transcode_query():
def test_transcode_query_ascii():
t = Transcoder('ascii')
eq_(t.transcode_query('a'), 'a')
assert t.transcode_query('a') == 'a'
View
35 tox.ini
@@ -1,16 +1,13 @@
[tox]
envlist =
py26,py27,py32,py33,py34,py35,pypy,pypy3,
{py2,py3}-docs,
{py2,py3}-cover,coverage
py27,py33,py34,py35,pypy,pypy3,
docs,{py2,py3}-cover,coverage
[testenv]
# Most of these are defaults but if you specify any you can't fall back
# to defaults for others.
basepython =
py26: python2.6
py27: python2.7
py32: python3.2
py33: python3.3
py34: python3.4
py35: python3.5
@@ -21,27 +18,28 @@ basepython =
commands =
pip install webob[testing]
nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
py.test tests --junitxml=pytest-{envname}.xml {posargs:}
[testenv:py2-cover]
[py-cover]
commands =
pip install webob[testing]
coverage run --source=webob {envbindir}/nosetests
coverage xml -o coverage-py2.xml
py.test tests --cov-report term-missing --cov=webob
[testenv:py2-cover]
commands =
{[py-cover]commands}
setenv =
COVERAGE_FILE=.coverage.py2
[testenv:py3-cover]
commands =
pip install webob[testing]
coverage run --source=webob {envbindir}/nosetests
coverage xml -o coverage-py3.xml
{[py-cover]commands}
setenv =
COVERAGE_FILE=.coverage.py3
[testenv:coverage]
basepython = python3.4
commands =
basepython = python3.5
commands =
coverage erase
coverage combine
coverage xml
@@ -51,13 +49,8 @@ deps =
setenv =
COVERAGE_FILE=.coverage
[testenv:py2-docs]
whitelist_externals = make
commands =
pip install webob[docs]
make -C docs html epub BUILDDIR={envdir} "SPHINXOPTS=-W -E"
[testenv:py3-docs]
[testenv:docs]
basepython = python3.5
whitelist_externals = make
commands =
pip install webob[docs]
View
@@ -14,19 +14,11 @@
from webob.headers import _trans_name as header_to_key
from webob.util import (
header_docstring,
warn_deprecation,
)
part_re = re.compile(
r',\s*([^\s;,\n]+)(?:[^,]*?;\s*q=([0-9.]*))?')
def _warn_first_match():
# TODO: remove .first_match in version 1.3
warn_deprecation("Use best_match instead", '1.2', 3)
class Accept(object):
"""
Represents a generic ``Accept-*`` style header.
@@ -131,15 +123,6 @@ def quality(self, offer, modifier=1):
bestq = max(bestq, q * modifier)
return bestq or None
def first_match(self, offers):
"""
DEPRECATED
Returns the first allowed offered type. Ignores quality.
Returns the first offered type if nothing else matches; or if you include None
at the end of the match list then that will be returned.
"""
_warn_first_match()
def best_match(self, offers, default_match=None):
"""
Returns the best match in the sequence of offered types.
@@ -184,7 +167,6 @@ def _match(self, mask, offer):
return mask == '*' or offer.lower() == mask.lower()
class NilAccept(object):
MasterClass = Accept
@@ -220,9 +202,6 @@ def __contains__(self, item):
def quality(self, offer, default_quality=1):
return 0
def first_match(self, offers): # pragma: no cover
_warn_first_match()
def best_match(self, offers, default_match=None):
best_quality = -1
best_offer = default_match
@@ -274,7 +253,7 @@ class MIMEAccept(Accept):
def parse(value):
for mask, q in Accept.parse(value):
try:
mask_major, mask_minor = map(lambda x: x.lower(), mask.split('/'))
mask_major, mask_minor = [x.lower() for x in mask.split('/')]
except ValueError:
continue
if mask_major == '*' and mask_minor != '*':
@@ -299,17 +278,58 @@ def accept_html(self):
def _match(self, mask, offer):
"""
Check if the offer is covered by the mask
``offer`` may contain wildcards to facilitate checking if a
``mask`` would match a 'permissive' offer.
Wildcard matching forces the match to take place against the
type or subtype of the mask and offer (depending on where
the wildcard matches)
"""
_check_offer(offer)
if '*' not in mask:
return offer.lower() == mask.lower()
elif mask == '*/*':
# Match if comparisons are the same or either is a complete wildcard
if (mask.lower() == offer.lower() or
'*/*' in (mask, offer) or
'*' == offer):
return True
else:
assert mask.endswith('/*')
mask_major = mask[:-2].lower()
offer_major = offer.split('/', 1)[0].lower()
return offer_major == mask_major
# Set mask type with wildcard subtype for malformed masks
try:
mask_type, mask_subtype = [x.lower() for x in mask.split('/')]
except ValueError:
mask_type = mask
mask_subtype = '*'
# Set offer type with wildcard subtype for malformed offers
try:
offer_type, offer_subtype = [x.lower() for x in offer.split('/')]
except ValueError:
offer_type = offer
offer_subtype = '*'
if mask_subtype == '*':
# match on type only
if offer_type == '*':
return True
else:
return mask_type.lower() == offer_type.lower()
if mask_type == '*':
# match on subtype only
if offer_subtype == '*':
return True
else:
return mask_subtype.lower() == offer_subtype.lower()
if offer_subtype == '*':
# match on type only
return mask_type.lower() == offer_type.lower()
if offer_type == '*':
# match on subtype only
return mask_subtype.lower() == offer_subtype.lower()
return offer.lower() == mask.lower()
class MIMENilAccept(NilAccept):
@@ -320,13 +340,12 @@ def _check_offer(offer):
raise ValueError("The application should offer specific types, got %r" % offer)
def accept_property(header, rfc_section,
AcceptClass=Accept, NilClass=NilAccept
):
key = header_to_key(header)
doc = header_docstring(header, rfc_section)
#doc += " Converts it as a %s." % convert_name
# doc += " Converts it as a %s." % convert_name
def fget(req):
value = req.environ.get(key)
if not value:
View
@@ -59,7 +59,7 @@ def __str__(self):
return 'bytes=%s-%s' % (s, e-1)
def __repr__(self):
return '%s(%r, %r)' % (
return '<%s bytes %r-%r>' % (
self.__class__.__name__,
self.start, self.end)
View
@@ -233,6 +233,13 @@ def app(req):
wrapped = restrict_ip(app, ips=['127.0.0.1'])
Or as a decorator::
@restrict_ip(ips=['127.0.0.1'])
@wsgify
def wrapped_app(req):
return 'hi'
Or if you want to write output-rewriting middleware::
@wsgify.middleware
@@ -293,7 +300,7 @@ def __repr__(self):
return '<%s at %s wrapping %r>' % (self.__class__.__name__, id(self),
self.middleware)
def __call__(self, app, **config):
def __call__(self, app=None, **config):
kw = self.kw.copy()
kw.update(config)
return self.wrapper_class.middleware(self.middleware, app, **kw)
View
@@ -138,6 +138,9 @@ def fget(r):
def fset(r, value):
fdel(r)
if value is not None:
if '\n' in value or '\r' in value:
raise ValueError('Header value may not contain control characters')
if isinstance(value, text_type) and not PY3:
value = value.encode('latin-1')
r._headerlist.append((header, value))
View
@@ -165,10 +165,12 @@
"""
import json
from string import Template
import re
import sys
from webob.acceptparse import MIMEAccept
from webob.compat import (
class_types,
text_,
@@ -177,15 +179,20 @@
)
from webob.request import Request
from webob.response import Response
from webob.util import (
html_escape,
warn_deprecation,
)
from webob.util import html_escape
tag_re = re.compile(r'<.*?>', re.S)
br_re = re.compile(r'<br.*?>', re.I|re.S)
br_re = re.compile(r'<br.*?>', re.I | re.S)
comment_re = re.compile(r'<!--|-->')
def lazify(func):
class _lazyfied(object):
def __init__(self, s):
self._s = s
def __str__(self):
return func(self._s)
return _lazyfied
def no_escape(value):
if value is None:
return ''
@@ -250,7 +257,7 @@ class WSGIHTTPException(Response, HTTPException):
empty_body = False
def __init__(self, detail=None, headers=None, comment=None,
body_template=None, **kw):
body_template=None, json_formatter=None, **kw):
Response.__init__(self,
status='%s %s' % (self.code, self.title),
**kw)
@@ -265,11 +272,14 @@ def __init__(self, detail=None, headers=None, comment=None,
if self.empty_body:
del self.content_type
del self.content_length
if json_formatter is not None:
self.json_formatter = json_formatter
def __str__(self):
return self.detail or self.explanation
def _make_body(self, environ, escape):
escape = lazify(escape)
args = {
'explanation': escape(self.explanation),
'detail': escape(self.detail or ''),
@@ -300,32 +310,45 @@ def html_body(self, environ):
return self.html_template_obj.substitute(status=self.status,
body=body)
def json_formatter(self, body, status, title, environ):
return {'message': body,
'code': status,
'title': title}
def json_body(self, environ):
body = self._make_body(environ, no_escape)
jsonbody = self.json_formatter(body=body, status=self.status,
title=self.title, environ=environ)
return json.dumps(jsonbody)
def generate_response(self, environ, start_response):
if self.content_length is not None:
del self.content_length
headerlist = list(self.headerlist)
accept = environ.get('HTTP_ACCEPT', '')
if accept and 'html' in accept or '*/*' in accept:
accept_value = environ.get('HTTP_ACCEPT', '')
accept = MIMEAccept(accept_value)
match = accept.best_match(['text/html', 'application/json'])
if match == 'text/html':
content_type = 'text/html'
body = self.html_body(environ)
elif match == 'application/json':
content_type = 'application/json'
body = self.json_body(environ)
else:
content_type = 'text/plain'
body = self.plain_body(environ)
extra_kw = {}
if isinstance(body, text_type):
extra_kw.update(charset='utf-8')
resp = Response(body,
status=self.status,
headerlist=headerlist,
content_type=content_type,
**extra_kw
)
status=self.status,
headerlist=headerlist,
content_type=content_type,
)
resp.content_type = content_type
return resp(environ, start_response)
def __call__(self, environ, start_response):
is_head = environ['REQUEST_METHOD'] == 'HEAD'
if self.body or self.empty_body or is_head:
if self.has_body or self.empty_body or is_head:
app_iter = Response.__call__(self, environ, start_response)
else:
app_iter = self.generate_response(environ, start_response)
@@ -481,6 +504,9 @@ def __init__(self, detail=None, headers=None, comment=None,
detail=detail, headers=headers, comment=comment,
body_template=body_template)
if location is not None:
if '\n' in location or '\r' in location:
raise ValueError('Control characters are not allowed in location')
self.location = location
if add_slash:
raise TypeError(
View
@@ -59,7 +59,6 @@
serialize_int,
serialize_range,
upath_property,
deprecated_property,
)
from webob.etag import (
@@ -220,7 +219,7 @@ def decode(self, charset=None, errors='strict'):
)
if content_type == 'application/x-www-form-urlencoded':
r.body = bytes_(t.transcode_query(native_(r.body)))
r.body = bytes_(t.transcode_query(native_(self.body)))
return r
elif content_type != 'multipart/form-data':
return r
@@ -1161,14 +1160,6 @@ def as_bytes(self, skip_body=False):
# HTTP clearly specifies CRLF
return b'\r\n'.join(parts)
def as_string(self, skip_body=False):
# TODO: Remove in 1.4
warn_deprecation(
"Please use req.as_bytes",
'1.3',
self._setattr_stacklevel
)
def as_text(self):
bytes = self.as_bytes()
return bytes.decode(self.charset)
@@ -1187,15 +1178,6 @@ def from_bytes(cls, b):
raise ValueError("The string contains more data than expected")
return r
@classmethod
def from_string(cls, b):
# TODO: Remove in 1.4
warn_deprecation(
"Please use req.from_bytes",
'1.3',
cls._setattr_stacklevel
)
@classmethod
def from_text(cls, s):
b = bytes_(s, 'utf-8')
@@ -1731,11 +1713,3 @@ def transcode_fs(self, fs, content_type):
fout=io.BytesIO()
)
return fout
# TODO: remove in 1.4
for _name in 'GET POST params cookies'.split():
_str_name = 'str_'+_name
_prop = deprecated_property(
None, _str_name,
"disabled starting WebOb 1.2, use %s instead" % _name, '1.2')
setattr(BaseRequest, _str_name, _prop)
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -47,11 +47,11 @@ def header_docstring(header, rfc_section):
def warn_deprecation(text, version, stacklevel):
# version specifies when to start raising exceptions instead of warnings
if version in ('1.2', '1.3', '1.4'):
if version in ('1.2', '1.3', '1.4', '1.5', '1.6', '1.7'):
raise DeprecationWarning(text)
else:
cls = DeprecationWarning
warnings.warn(text, cls, stacklevel=stacklevel+1)
warnings.warn(text, cls, stacklevel=stacklevel + 1)
status_reasons = {
# Status Codes