diff options
author | pje <pje@571e12c6-e1fa-0310-aee7-ff1267fa46bd> | 2010-10-05 02:04:36 +0000 |
---|---|---|
committer | pje <pje@571e12c6-e1fa-0310-aee7-ff1267fa46bd> | 2010-10-05 02:04:36 +0000 |
commit | 8a933141aaa3a3545bc1f9c71f977aed28836ac7 (patch) | |
tree | 375e7402885f1e1631ba2dfda504765d3cb09c7a | |
parent | f4c188a87b680e260b2658ee51ab31405064526d (diff) | |
download | wsgiref-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-x | README.txt | 7 | ||||
-rwxr-xr-x | docs/libwsgiref.tex | 41 | ||||
-rwxr-xr-x | docs/ref.tex | 4 | ||||
-rwxr-xr-x | setup.cfg | 4 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | wsgiref/handlers.py | 6 | ||||
-rw-r--r-- | wsgiref/util.py | 82 | ||||
-rwxr-xr-x | wsgiref/validate.py | 22 |
8 files changed, 153 insertions, 15 deletions
@@ -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 + @@ -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.") |