summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpje <pje@571e12c6-e1fa-0310-aee7-ff1267fa46bd>2010-10-05 02:04:36 +0000
committerpje <pje@571e12c6-e1fa-0310-aee7-ff1267fa46bd>2010-10-05 02:04:36 +0000
commit8a933141aaa3a3545bc1f9c71f977aed28836ac7 (patch)
tree375e7402885f1e1631ba2dfda504765d3cb09c7a
parentf4c188a87b680e260b2658ee51ab31405064526d (diff)
downloadwsgiref-8a933141aaa3a3545bc1f9c71f977aed28836ac7.tar.gz
WSGI 1.0.1, version bump, and new test() function.
git-svn-id: svn://svn.eby-sarna.com/svnroot/wsgiref@2689 571e12c6-e1fa-0310-aee7-ff1267fa46bd
-rwxr-xr-xREADME.txt7
-rwxr-xr-xdocs/libwsgiref.tex41
-rwxr-xr-xdocs/ref.tex4
-rwxr-xr-xsetup.cfg4
-rwxr-xr-xsetup.py2
-rw-r--r--wsgiref/handlers.py6
-rw-r--r--wsgiref/util.py82
-rwxr-xr-xwsgiref/validate.py22
8 files changed, 153 insertions, 15 deletions
diff --git a/README.txt b/README.txt
index 1934ea3..e181607 100755
--- a/README.txt
+++ b/README.txt
@@ -1,5 +1,8 @@
-This is a standalone release of the ``wsgiref`` library to be included in
-Python 2.5. For the standalone version's documentation, see:
+This is a standalone release of the ``wsgiref`` library, that provides
+validation support for WSGI 1.0.1 (PEP 3333) for Python versions < 3.2,
+and includes the new ``wsgiref.util.test()`` utility function.
+
+For this version's documentation, see:
HTML
http://peak.telecommunity.com/wsgiref_docs/
diff --git a/docs/libwsgiref.tex b/docs/libwsgiref.tex
index d797393..3b81e93 100755
--- a/docs/libwsgiref.tex
+++ b/docs/libwsgiref.tex
@@ -162,6 +162,47 @@ will also have a \method{close()} method, and it will invoke the
+\begin{funcdesc}{test}{app, environ=\code{\{\}}, form=\code{\{\}} **kw}
+Print the output of a WSGI app (e.g. for use in doctests)
+
+Runs \var{app} as a WSGI application and prints its output. If an untrapped
+error occurs in \var{app}, it drops into the \code{pdb} debugger's post-mortem
+debug shell (using \code{sys.__stdout__} if \code{sys.stdout} has been
+replaced).
+
+Any keyword arguments are added to the environment used to run \var{app}. If
+a keyword argument begins with \code{wsgi_}, the \code{_} is replaced with a
+\code{.}, so that you can set e.g. \code{wsgi.multithread} using a
+\code{wsgi_multithread} keyword argument.
+
+If a non-empty \var{form} dictionary is provided, it is treated as a collection
+of fields for a form \code{POST}. The \code{REQUEST_METHOD} will default to
+\code{POST}, and the default \code{CONTENT_LENGTH}, \code{CONTENT_TYPE}, and
+\code{wsgi.input} values will be appropriately set (but can still be
+overridden by explicit keyword arguments or the \var{environ} argument).
+
+Any \var{form} values that are not instances of \code{basestring} are assumed to
+be *sequences* of values, and will result in multiple name/value pairs
+being added to the encoded data sent to the application.
+
+Any WSGI-required variables that are not specified by \var{environ}, \var{form},
+or keyword arguments, are initialized to default values using the
+\function{setup_testing_defaults()} function.
+
+(\versionadded{0.2})
+
+\end{funcdesc}
+
+
+
+
+
+
+
+
+
+
+
\subsection{\module{wsgiref.headers} -- WSGI response header tools}
\declaremodule{}{wsgiref.headers}
diff --git a/docs/ref.tex b/docs/ref.tex
index d5ac69a..66d6af0 100755
--- a/docs/ref.tex
+++ b/docs/ref.tex
@@ -19,9 +19,9 @@
Email: \email{pje@telecommunity.com}
}
-\date{June 5, 2006} % update before release!
+\date{October 4, 2010} % update before release!
-\release{0.1} % release version; this is used to define the
+\release{0.2} % release version; this is used to define the
% \version macro
\makeindex % tell \index to actually write the .idx file
diff --git a/setup.cfg b/setup.cfg
new file mode 100755
index 0000000..2f3cd98
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = dev
+tag_svn_revision = 1
+
diff --git a/setup.py b/setup.py
index 31e3864..96353c4 100755
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ from setuptools import setup, find_packages
# Metadata
PACKAGE_NAME = "wsgiref"
-PACKAGE_VERSION = "0.1.2"
+PACKAGE_VERSION = "0.2"
setup(
name=PACKAGE_NAME,
diff --git a/wsgiref/handlers.py b/wsgiref/handlers.py
index 099371b..b40fd24 100644
--- a/wsgiref/handlers.py
+++ b/wsgiref/handlers.py
@@ -175,6 +175,8 @@ class BaseHandler:
elif self.headers is not None:
raise AssertionError("Headers already set!")
+ self.status = status
+ self.headers = self.headers_class(headers)
assert type(status) is StringType,"Status must be a string"
assert len(status)>=4,"Status must be at least 4 characters"
assert int(status[:3]),"Status message must begin w/3-digit code"
@@ -184,8 +186,6 @@ class BaseHandler:
assert type(name) is StringType,"Header names must be strings"
assert type(val) is StringType,"Header values must be strings"
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
- self.status = status
- self.headers = self.headers_class(headers)
return self.write
@@ -467,6 +467,7 @@ class CGIHandler(BaseCGIHandler):
"""
wsgi_run_once = True
+ os_environ = {} # Handle GAE and other multi-run CGI use cases
def __init__(self):
BaseCGIHandler.__init__(
@@ -488,5 +489,4 @@ class CGIHandler(BaseCGIHandler):
-
#
diff --git a/wsgiref/util.py b/wsgiref/util.py
index 9009b87..b711c0c 100644
--- a/wsgiref/util.py
+++ b/wsgiref/util.py
@@ -39,6 +39,88 @@ class FileWrapper:
+def test(app, environ={}, form={}, **kw):
+ """Print the output of a WSGI app (e.g. for use in doctests)
+
+ Runs `app` as a WSGI application and prints its output. If an untrapped
+ error occurs in `app`, it drops into the ``pdb`` debugger's post-mortem
+ debug shell (using ``sys.__stdout__`` if ``sys.stdout`` has been replaced).
+
+ Any keyword arguments are added to the environment used to run `app`. If
+ a keyword argument begins with ``wsgi_``, the ``_`` is replaced with a
+ ``.``, so that you can set e.g. ``wsgi.multithread`` using a
+ ``wsgi_multithread`` keyword argument.
+
+ If a non-empty `form` dictionary is provided, it is treated as a collection
+ of fields for a form ``POST``. The ``REQUEST_METHOD`` will default to
+ ``POST``, and the default ``CONTENT_LENGTH``, ``CONTENT_TYPE``, and
+ ``wsgi.input`` values will be appropriately set (but can still be
+ overridden by explicit keyword arguments or the `environ` argument).
+
+ Any `form` values that are not instances of ``basestring`` are assumed to
+ be *sequences* of values, and will result in multiple name/value pairs
+ being added to the encoded data sent to the application.
+
+ Any WSGI-required variables that are not specified by `environ`, `form`, or
+ keyword arguments, are initialized to default values using the
+ ``wsgiref.util.setup_testing_defaults()`` function.
+ """
+
+ from wsgiref.handlers import SimpleHandler
+ from StringIO import StringIO
+ from urllib import quote_plus
+
+ environ = environ.copy()
+ for k, v in kw.items():
+ if k.startswith('wsgi_'):
+ environ[k.replace('_','.',1)] = v
+ else:
+ environ[k] = v
+
+
+
+
+ if form:
+ encoded = []
+ for k, v in form.items():
+ if isinstance(v,basestring):
+ v = [v]
+ for v in v:
+ encoded.append('%s=%s' % (quote_plus(k), quote_plus(v)))
+ encoded = '&'.join(encoded)
+ environ.setdefault('wsgi.input', StringIO(encoded))
+ environ.setdefault('CONTENT_LENGTH', str(len(encoded)))
+ environ.setdefault('CONTENT_TYPE', 'application/x-www-form-urlencoded')
+ environ.setdefault('REQUEST_METHOD', 'POST')
+
+ setup_testing_defaults(environ)
+ stdout = StringIO()
+ stderr = environ['wsgi.errors']
+
+ def wrapper(env, start):
+ try:
+ return app(env, start)
+ except:
+ stdout = sys.stdout
+ try:
+ if stdout is not sys.__stdout__:
+ sys.stdout = sys.__stdout__
+ import pdb
+ pdb.post_mortem(sys.exc_info()[2])
+ finally:
+ sys.stdout = stdout
+ raise
+
+ SimpleHandler(
+ environ['wsgi.input'], stdout, stderr, environ,
+ environ['wsgi.multithread'], environ['wsgi.multiprocess']
+ ).run(wrapper)
+ print stdout.getvalue().replace('\r\n','\n')
+ if stderr.getvalue():
+ print "--- Log Output ---"
+ print stderr.getvalue().replace('\r\n','\n')
+
+
def guess_scheme(environ):
"""Return a guess for whether 'wsgi.url_scheme' should be 'http' or 'https'
"""
diff --git a/wsgiref/validate.py b/wsgiref/validate.py
index 23ab9f8..249507a 100755
--- a/wsgiref/validate.py
+++ b/wsgiref/validate.py
@@ -116,6 +116,11 @@ import sys
from types import DictType, StringType, TupleType, ListType
import warnings
+try:
+ bytes
+except NameError:
+ bytes = str
+
header_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9\-_]*$')
bad_header_value_re = re.compile(r'[\000-\037]')
@@ -191,12 +196,13 @@ class InputWrapper:
def read(self, *args):
assert_(len(args) <= 1)
v = self.input.read(*args)
- assert_(type(v) is type(""))
+ assert_(type(v) is bytes)
return v
- def readline(self):
- v = self.input.readline()
- assert_(type(v) is type(""))
+ def readline(self, *args):
+ assert_(len(args) <= 1)
+ v = self.input.readline(*args)
+ assert_(type(v) is bytes)
return v
def readlines(self, *args):
@@ -204,7 +210,7 @@ class InputWrapper:
lines = self.input.readlines(*args)
assert_(type(lines) is type([]))
for line in lines:
- assert_(type(line) is type(""))
+ assert_(type(line) is bytes)
return lines
def __iter__(self):
@@ -242,7 +248,7 @@ class WriteWrapper:
self.writer = wsgi_writer
def __call__(self, s):
- assert_(type(s) is type(""))
+ assert_(type(s) is bytes)
self.writer(s)
class PartialIteratorWrapper:
@@ -269,6 +275,8 @@ class IteratorWrapper:
assert_(not self.closed,
"Iterator read after closed")
v = self.iterator.next()
+ if type(v) is not bytes:
+ assert_(False, "Iterator yielded non-bytestring (%r)" % (v,))
if self.check_start_response is not None:
assert_(self.check_start_response,
"The application returns and we started iterating over its body, but start_response has not yet been called")
@@ -427,6 +435,6 @@ def check_iterator(iterator):
# Technically a string is legal, which is why it's a really bad
# idea, because it may cause the response to be returned
# character-by-character
- assert_(not isinstance(iterator, str),
+ assert_(not isinstance(iterator, (bytes,str)),
"You should not return a string as your application iterator, "
"instead return a single-item list containing that string.")