[Checkins] SVN: zope.httpform/trunk/ Used documentation and design ideas from repoze.monty. Thanks, Chris and Agendaless.

Shane Hathaway shane at hathawaymix.org
Fri Feb 6 23:42:05 EST 2009


Log message for revision 96200:
  Used documentation and design ideas from repoze.monty.  Thanks, Chris and Agendaless.
  

Changed:
  U   zope.httpform/trunk/CHANGES.txt
  A   zope.httpform/trunk/LICENSE.txt
  U   zope.httpform/trunk/README.txt
  U   zope.httpform/trunk/src/zope/httpform/README.txt
  U   zope.httpform/trunk/src/zope/httpform/__init__.py
  U   zope.httpform/trunk/src/zope/httpform/parser.py

-=-
Modified: zope.httpform/trunk/CHANGES.txt
===================================================================
--- zope.httpform/trunk/CHANGES.txt	2009-02-06 21:03:18 UTC (rev 96199)
+++ zope.httpform/trunk/CHANGES.txt	2009-02-07 04:42:04 UTC (rev 96200)
@@ -9,6 +9,9 @@
 - Relaxed the requirement for REQUEST_METHOD because the zope.publisher
   tests do not set it.
 
+- Used documentation and design ideas from repoze.monty.  Thanks,
+  Chris and Agendaless.
+
 Version 1.0.0 (2009-02-06)
 --------------------------
 

Added: zope.httpform/trunk/LICENSE.txt
===================================================================
--- zope.httpform/trunk/LICENSE.txt	                        (rev 0)
+++ zope.httpform/trunk/LICENSE.txt	2009-02-07 04:42:04 UTC (rev 96200)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+  NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  DAMAGE.

Modified: zope.httpform/trunk/README.txt
===================================================================
--- zope.httpform/trunk/README.txt	2009-02-06 21:03:18 UTC (rev 96199)
+++ zope.httpform/trunk/README.txt	2009-02-07 04:42:04 UTC (rev 96200)
@@ -1,68 +1,239 @@
 
+.. contents::
 
-This package provides a WSGI-oriented HTTP form parser with interesting
-features to make form handling easier.  This functionality has lived
-for a long time inside Zope's publisher, but has been broken out into
-a separate package to make it easier to test, explain, understand, and use.
+``zope.httpform`` is a library that, given a WSGI or CGI environment
+dictionary, will return a dictionary back containing converted
+form/query string elements.  The form and query string elements
+contained in the environment are converted into simple Python types when
+the form element names are decorated with special suffixes.  For
+example, in an HTML form that you'd like this library to process,
+you might say::
 
-The FormParser class uses Python's standard ``cgi.FieldStorage`` class,
-but is easier to use than FieldStorage.  The parser converts field names
-and values to Unicode, handles file uploads in a graceful manner, and
-allows field name suffixes that tell the parser how to handle each field.
-The available suffixes are:
+  <form action=".">
+     Age : <input type="hidden" name="age:int" value="20"/>
+     <input type="submit" name="Submit"/>
+  </form>
 
-    - ``:int``      -- convert to an integer
-    - ``:float``    -- convert to a float
-    - ``:long``     -- convert to a long integer
-    - ``:string``   -- convert to a string (useful for uploads)
-    - ``:required`` -- raise ValueError if the field is not provided
-    - ``:tokens``   -- split the input on whitespace characters
-    - ``:lines``    -- split multiline input into a list of lines
-    - ``:text``     -- normalize line endings of multiline text
-    - ``:boolean``  -- true if nonempty, false if empty
-    - ``:list``     -- make a list even if there is only one value
-    - ``:tuple``    -- make a tuple
-    - ``:action``   -- specify the form action
-    - ``:method``   -- same as ``:action``
-    - ``:default``  -- provide a default value
-    - ``:record``   -- generate a record object
-    - ``:records``  -- generate a list of record object
-    - ``:ignore_empty``   -- discard the field value if it's empty
-    - ``:default_action`` -- specifies a default form action
-    - ``:default_method`` -- same as ``:default_action``
+Likewise, in the query string of the URL, you could put::
 
-Here are some examples of ways to use these suffixes.
+   http://example.com/mypage?age:int=20
 
-* Using this package, you can provide a default for a field in an HTML form::
+In both of these cases, when provided the WSGI or CGI environment,
+and asked to return a value, this library will return a dictionary
+like so::
 
-    <input type="text" name="country:ignore_empty" />
-    <input type="hidden" name="country:default" value="Chile" />
+  {'age':20}
 
-  The FormData class in this package will convert that form submission
-  to a mapping containing a Unicode value for the ``country`` field.
-  If the user leaves the field empty, the ``country`` field will have
-  the value of ``"Chile"``.
+This functionality has lived for a long time inside Zope, but now
+Zope's publisher uses this library.
 
-* You can ensure that certain variables are placed
-  in a list, even when only one value is selected::
+Form/Query String Element Parsing
+---------------------------------
 
-    <select name="cars:list" multiple="multiple">
-    <option value="volvo">Volvo</option>
-    <option value="saab">Saab</option>
-    <option value="mercedes">Mercedes</option>
-    <option value="audi">Audi</option>
-    </select>
+``zope.httpform`` provides a way for you to specify form input types in
+the form, rather than in your application code. Instead of converting
+the *age* variable to an integer in a controller or view, you can
+indicate that it is an integer in the form itself::
 
-* You can group data into record objects, which is very useful for complex
-  forms::
+       Age <input type="text" name="age:int" />
 
-    <input type="text" name="shipping.name:record" />
-    <input type="text" name="shipping.address:record" />
-    <input type="text" name="shipping.phone:record" />
-    <input type="text" name="billing.name:record" />
-    <input type="text" name="billing.address:record" />
-    <input type="text" name="billing.phone:record" />
+The ``:int`` appended to the form input name tells this library to
+convert the form input to an integer when it is invoked.  If the
+user of your form types something that cannot be converted to an
+integer in the above case (such as "22 going on 23") then this library
+will raise a ValueError.
 
-You can do a lot more with these suffixes.  See
-``src/zope/httpform/README.txt`` for a demonstration and test of all
-features.
+Here is a list of the standard parameter converters.
+
+``:boolean``
+
+  Converts a variable to true or false.  Empty strings are evaluated as
+  false and non-empty strings are evaluated as true.
+
+``:int``
+
+  Converts a variable to an integer.
+
+``:long``
+
+  Converts a variable to a long integer.
+
+``:float``
+
+  Converts a variable to a floating point number.
+
+``:string``
+
+  Converts a variable to a string.  Most variables are strings already,
+  so this converter is not often used except to simplify file uploads.
+
+``:text``
+
+  Converts a variable to a string with normalized line breaks.
+  Different browsers on various platforms encode line endings
+  differently, so this script makes sure the line endings are
+  consistent, regardless of how they were encoded by the browser.
+
+``:list``
+
+  Converts a variable to a Python list.
+
+``:tuple``
+
+  Converts a variable to a Python tuple.
+
+``:tokens``
+
+  Converts a string to a list by breaking it on white spaces.
+
+``:lines``
+
+  Converts a string to a list by breaking it on new lines.
+
+``:required``
+
+  Raises an exception if the variable is not present.
+
+``:ignore_empty``
+
+  Excludes the variable from the form data if the variable is an empty
+  string.
+
+These converters all work in more or less the same way to coerce a
+form variable, which is a string, into another specific type.
+
+The ``:list`` and ``:tuple`` converters can be used in combination with
+other converters.  This allows you to apply additional converters to
+each element of the list or tuple.  Consider this form::
+
+       <form action=".">
+
+         <p>I like the following numbers</p>
+
+         <input type="checkbox" name="favorite_numbers:list:int"
+         value="1" /> One<br />
+
+         <input type="checkbox" name="favorite_numbers:list:int"
+         value="2" />Two<br />
+
+         <input type="checkbox" name="favorite_numbers:list:int"
+         value="3" />Three<br />
+
+         <input type="checkbox" name="favorite_numbers:list:int"
+         value="4" />Four<br />
+
+         <input type="checkbox" name="favorite_numbers:list:int"
+         value="5" />5<br />
+
+         <input type="submit" />
+       </form>
+
+By using the ``:list`` and ``:int`` converters together, this library
+will convert each selected item to an integer and then combine all
+selected integers into a list named *favorite_numbers*.
+
+A more complex type of form conversion is to convert a series of
+inputs into *records*. Records are structures that have
+attributes. Using records, you can combine a number of form inputs
+into one variable with attributes.  The available record converters
+are:
+
+``:record``
+
+  Converts a variable to a record attribute.
+
+``:records``
+
+  Converts a variable to a record attribute in a list of records.
+
+``:default``
+
+  Provides a default value for a record attribute if the variable is
+  empty.
+
+``:ignore_empty``
+
+  Skips a record attribute if the variable is empty.
+
+Here are some examples of how these converters are used::
+
+       <form action=".">
+
+         First Name <input type="text" name="person.fname:record" /><br />
+         Last Name <input type="text" name="person.lname:record" /><br />
+         Age <input type="text" name="person.age:record:int" /><br />
+
+         <input type="submit" />
+       </form>
+
+If the information represented by this form post is passed to
+``zope.httpform``, the resulting dictionary will container one parameter,
+*person*. The *person* variable will have the attributes *fname*,
+*lname* and *age*. Here's an example of how you might use
+``zope.httpform`` to process the form post (assuming you have a WSGI
+or CGI environment in hand)::
+
+  from zope.httpform import parse
+
+  info = parse(environ, environ['wsgi.input'])
+  person = info['person']
+  full_name = "%s %s" % (person.fname, person.lname)
+  if person.age < 21:
+      return ("Sorry, %s. You are not old enough to adopt an "
+              "aardvark." % full_name)
+  return "Thanks, %s. Your aardvark is on its way." % full_name
+
+The *records* converter works like the *record* converter except
+that it produces a list of records, rather than just one. Here is
+an example form::
+
+  <form action=".">
+
+    <p>Please, enter information about one or more of your next of
+    kin.</p>
+
+    <p>
+      First Name <input type="text" name="people.fname:records" />
+      Last Name <input type="text" name="people.lname:records" />
+    </p>
+
+    <p>
+      First Name <input type="text" name="people.fname:records" />
+      Last Name <input type="text" name="people.lname:records" />
+    </p>
+
+    <p>
+      First Name <input type="text" name="people.fname:records" />
+      Last Name <input type="text" name="people.lname:records" />
+    </p>
+
+    <input type="submit" />
+  </form>
+
+If you call ``zope.httpform``'s parse method with the information
+from this form post, a dictionary will be returned from it with a
+variable called *people* that is a list of records. Each record will
+have *fname* and *lname* attributes.  Note the difference between the
+*records* converter and the *list:record* converter: the former would
+create a list of records, whereas the latter would produce a single
+record whose attributes *fname* and *lname* would each be a list of
+values.
+
+The order of combined modifiers does not matter; for example,
+``:int:list`` is identical to ``:list:int``.
+
+Gotchas
+-------
+
+The file pointer passed to ``zope.httpform.parse()`` will be
+consumed.  For all intents and purposes this means you should make a
+tempfile copy of the ``wsgi.input`` file pointer before calling
+``parse()`` if you intend to use the POST file input data in your
+application.
+
+Acknowledgements
+----------------
+
+This documentation was graciously donated by the team at
+Agendaless Consulting.  The ``zope.httpform`` package is
+expected to replace the ``repoze.monty`` package.

Modified: zope.httpform/trunk/src/zope/httpform/README.txt
===================================================================
--- zope.httpform/trunk/src/zope/httpform/README.txt	2009-02-06 21:03:18 UTC (rev 96199)
+++ zope.httpform/trunk/src/zope/httpform/README.txt	2009-02-07 04:42:04 UTC (rev 96200)
@@ -7,32 +7,32 @@
 Basic Usage
 -----------
 
-The FormParser class expects a subset of a WSGI environment.  Start with
+The parser expects a subset of a WSGI environment.  Start with
 a simple form.
 
-    >>> import pprint
-    >>> from zope.httpform import FormParser
+    >>> from pprint import pprint
+    >>> from zope.httpform import parse
     >>> env = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'x=1&y=my+data&z='}
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': u'1', u'y': u'my data', u'z': u''}
 
 Now let's start using some of the features of this package.  Use the `:int`
 suffix on the variable name:
 
     >>> env['QUERY_STRING'] = 'x:int=1&y:int=2'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': 1, u'y': 2}
 
 Floating point and long integers work too:
 
     >>> env['QUERY_STRING'] = 'x:float=1&y:float=2&z:long=3&zz:long=4L'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': 1.0, u'y': 2.0, u'z': 3L, u'zz': 4L}
 
 The `:boolean` suffix is good for HTML checkboxes:
 
     >>> env['QUERY_STRING'] = 'x:boolean=checked&y:boolean='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': True, u'y': False}
 
 Lists and Tuples
@@ -41,51 +41,51 @@
 What happens if variables get repeated?
 
     >>> env['QUERY_STRING'] = 'x=0&x=a&x=b&x:int=1&x:float=2'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'0', u'a', u'b', 1, 2.0]}
 
 That's reasonable, but it's even better to use the `:list` suffix so that
 field values are a list even when they occur only once.
 
     >>> env['QUERY_STRING'] = 'x:int:list=1'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [1]}
 
 Another variation:
 
     >>> env['QUERY_STRING'] = 'x:list:int=1'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [1]}
 
 Empty values are preserved:
 
     >>> env['QUERY_STRING'] = 'x:list=a&x:list='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'a', u'']}
 
 Order is preserved:
 
     >>> env['QUERY_STRING'] = 'x:list=c&x:list=b&x:list=a'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'c', u'b', u'a']}
 
 Empty values are not preserved if you use the ignore_empty suffix:
 
     >>> env['QUERY_STRING'] = 'x:list:ignore_empty=a&x:list:ignore_empty='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'a']}
 
 Use `:tuple` to generate a tuple instead of a list.  Note that the order
 of the field values is always preserved.
 
     >>> env['QUERY_STRING'] = 'x:int:tuple=2&x:int:tuple=1'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': (2, 1)}
 
 A value can become a list:
 
     >>> env['QUERY_STRING'] = 'x:int=0&x:int:list=1'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [0, 1]}
 
 Default Values
@@ -94,39 +94,39 @@
 Sometimes it's useful for a form to provide a default value.
 
     >>> env['QUERY_STRING'] = 'country:default=United+States'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'country': u'United States'}
     >>> env['QUERY_STRING'] = 'country:default=United+States&country=Ireland'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'country': u'Ireland'}
 
 An empty value overrides a default value, unless the potentially
 empty value uses the `:ignore_empty` suffix.
 
     >>> env['QUERY_STRING'] = 'country:default=US&country='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'country': u''}
     >>> env['QUERY_STRING'] = 'country:default=US&country:ignore_empty='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'country': u'US'}
 
 A default value takes the place of an empty value.
 
     >>> env['QUERY_STRING'] = 'x:int:default=0&x:int='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': 0}
 
 A default list value will be added to the list unless it has already
 been added.
 
     >>> env['QUERY_STRING'] = 'x:list:default=always'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'always']}
     >>> env['QUERY_STRING'] = 'x:list:default=always&x:list=always'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'always']}
     >>> env['QUERY_STRING'] = 'x:list:default=always&x:list=never'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'never', u'always']}
 
 Required Values
@@ -135,7 +135,7 @@
 The `:required` suffix raises `ValueError` if the field is left empty.
 
     >>> env['QUERY_STRING'] = 'x:required='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: No input for required field
@@ -143,10 +143,10 @@
 Don't leave it empty.
 
     >>> env['QUERY_STRING'] = 'x:required=123'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': u'123'}
     >>> env['QUERY_STRING'] = 'x:int:required=123'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': 123}
 
 Simple Text Handling
@@ -155,19 +155,19 @@
 Use `:tokens` to split the input on whitespace.
 
     >>> env['QUERY_STRING'] = 'x:tokens=a+b++c%0Ad'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'a', u'b', u'c', u'd']}
 
 Use `:text` to normalize multiline input.  This is helpful for textarea tags.
 
     >>> env['QUERY_STRING'] = 'stuff:text=line1%0D%0Aline2%0D%0A'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'stuff': u'line1\nline2\n'}
 
 Use `:lines` to convert multiline input to a series of lines.
 
     >>> env['QUERY_STRING'] = 'x:lines=line1%0D%0A%0D%0Aline2%0D%0A'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': [u'line1', u'', u'line2']}
 
 Records
@@ -176,8 +176,8 @@
 The :record suffix produces a record object.
 
     >>> env['QUERY_STRING'] = 'x.a:int:record=1&x.b:int:record=2'
-    >>> form = FormParser(env).parse()
-    >>> pprint.pprint(form)
+    >>> form = parse(env)
+    >>> pprint(form)
     {u'x': {'a': 1, 'b': 2}}
 
 You can access record values using either attribute or item access.
@@ -202,7 +202,7 @@
 disallowed.
 
     >>> env['QUERY_STRING'] = 'x.keys:record='
-    >>> FormParser(env).parse()
+    >>> parse(env)
     Traceback (most recent call last):
     ...
     AttributeError: Illegal record attribute name: keys
@@ -214,7 +214,7 @@
     >>> q += '&billing.address1:record=Apt+29'
     >>> q += '&billing.address2:record=75+First+St'
     >>> env['QUERY_STRING'] = q
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'billing': {'address1': u'Apt 29', 'address2': u'75 First St'},
      u'shipping': {'address1': u'Apt 1', 'address2': u'75 First St'}}
 
@@ -225,7 +225,7 @@
     >>> q += '&points.x:float:records=11'
     >>> q += '&points.y:float:records=-2'
     >>> env['QUERY_STRING'] = q
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'points': [{'x': 1.0, 'y': 2.0}, {'x': 11.0, 'y': -2.0}]}
 
 A record can contain a tuple.
@@ -235,7 +235,7 @@
     >>> q += '&segment.p1:tuple:record=10'
     >>> q += '&segment.p1:tuple:record=11'
     >>> env['QUERY_STRING'] = q
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'segment': {'p0': (u'0', u'0'), 'p1': (u'10', u'11')}}
 
 You can put a list or tuple inside a record list, but you need to use
@@ -254,7 +254,7 @@
     >>> q += '&points.axes:tuple:int:records=2'
     >>> q += '&points.axes:tuple:int:records=3'
     >>> env['QUERY_STRING'] = q
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'points': [{'': u'', 'axes': (0, 0, 0), 'color': 0},
                  {'': u'', 'axes': (1, 2, 3), 'color': 0}]}
 
@@ -266,7 +266,7 @@
     >>> q += '&friends.name:records=Charlie'
     >>> q += '&friends.birthdate:records=1/1/1'
     >>> env['QUERY_STRING'] = q
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'friends': [{'birthdate': u'unspecified', 'name': u'Alice'},
                   {'birthdate': u'unspecified', 'name': u'Bob'},
                   {'birthdate': u'1/1/1', 'name': u'Charlie'}]}
@@ -279,7 +279,7 @@
     >>> q += '&prefs.address:record=123+Grant+Ave'
     >>> q += '&prefs.age:int:record='
     >>> env['QUERY_STRING'] = q
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'prefs': {'address': u'123 Grant Ave', 'age': 100, 'name': u'unnamed'}}
 
 Actions
@@ -291,6 +291,7 @@
 
 By default there is no action:
 
+    >>> from zope.httpform import FormParser
     >>> env['QUERY_STRING'] = ''
     >>> parser = FormParser(env)
     >>> parser.parse()
@@ -302,7 +303,7 @@
 
     >>> env['QUERY_STRING'] = 'y:int=1&:action=getX'
     >>> parser = FormParser(env)
-    >>> pprint.pprint(parser.parse())
+    >>> pprint(parser.parse())
     {u'': u'getX', u'y': 1}
     >>> parser.action
     u'getX'
@@ -315,7 +316,7 @@
 
     >>> env['QUERY_STRING'] = 'stop:action=Parar'
     >>> parser = FormParser(env)
-    >>> pprint.pprint(parser.parse())
+    >>> pprint(parser.parse())
     {u'stop': u'Parar'}
     >>> parser.action
     u'stop'
@@ -324,7 +325,7 @@
 
     >>> env['QUERY_STRING'] = 'stop:method=Parar'
     >>> parser = FormParser(env)
-    >>> pprint.pprint(parser.parse())
+    >>> pprint(parser.parse())
     {u'stop': u'Parar'}
     >>> parser.action
     u'stop'
@@ -333,13 +334,13 @@
 
     >>> env['QUERY_STRING'] = ':default_action=next'
     >>> parser = FormParser(env)
-    >>> pprint.pprint(parser.parse())
+    >>> pprint(parser.parse())
     {u'': u'next'}
     >>> parser.action
     u'next'
     >>> env['QUERY_STRING'] = 'next:default_action=&prev:action='
     >>> parser = FormParser(env)
-    >>> pprint.pprint(parser.parse())
+    >>> pprint(parser.parse())
     {u'next': u'', u'prev': u''}
     >>> parser.action
     u'prev'
@@ -351,43 +352,43 @@
 but unrecognized suffixes get ignored.
 
     >>> env['QUERY_STRING'] = 'x:0:int=1&y:complex=2'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x:0': 1, u'y': u'2'}
 
 The int, long, and float types require a valid value.
 
     >>> env['QUERY_STRING'] = 'x:int='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: Empty entry when integer expected
 
     >>> env['QUERY_STRING'] = 'x:int=z'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: An integer was expected in the value 'z'
 
     >>> env['QUERY_STRING'] = 'x:long='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: Empty entry when integer expected
 
     >>> env['QUERY_STRING'] = 'x:long=z'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: A long integer was expected in the value 'z'
 
     >>> env['QUERY_STRING'] = 'x:float='
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: Empty entry when float expected
 
     >>> env['QUERY_STRING'] = 'x:float=z'
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     Traceback (most recent call last):
     ...
     ValueError: A float was expected in the value 'z'
@@ -403,13 +404,13 @@
     >>> env = {'REQUEST_METHOD': 'POST',
     ...        'CONTENT_TYPE': 'application/x-www-form-urlencoded',
     ...        'wsgi.input': input_fp}
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {u'x': 1, u'y': 2}
 
 The query string is ignored on POST.
 
     >>> env = {'REQUEST_METHOD': 'POST', 'QUERY_STRING': 'x=1'}
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {}
 
 No form parsing is done for content types that don't look like forms.
@@ -418,7 +419,7 @@
     >>> env = {'REQUEST_METHOD': 'POST',
     ...        'CONTENT_TYPE': 'text/xml',
     ...        'wsgi.input': input_fp}
-    >>> pprint.pprint(FormParser(env).parse())
+    >>> pprint(parse(env))
     {}
 
 Uploading Files
@@ -446,8 +447,8 @@
     >>> env = {'REQUEST_METHOD': 'POST',
     ...        'CONTENT_TYPE': content_type,
     ...        'wsgi.input': StringIO(input_body)}
-    >>> form = FormParser(env).parse()
-    >>> pprint.pprint(form)
+    >>> form = parse(env)
+    >>> pprint(form)
     {u'field1': [u'Joe Blow'],
      u'pics': <zope.httpform.parser.FileUpload object at ...>}
 
@@ -466,7 +467,7 @@
     <rfc822.Message instance at ...>
     >>> pics.headers['Content-Type']
     'text/plain'
-    >>> pprint.pprint(dict(pics.headers))
+    >>> pprint(dict(pics.headers))
     {'content-disposition': 'form-data; name="pics"; filename="file1.txt"',
      'content-type': 'text/plain'}
 
@@ -486,8 +487,8 @@
     >>> env = {'REQUEST_METHOD': 'POST',
     ...        'CONTENT_TYPE': content_type,
     ...        'wsgi.input': StringIO(input_body)}
-    >>> form = FormParser(env).parse()
-    >>> pprint.pprint(form)
+    >>> form = parse(env)
+    >>> pprint(form)
     {u'pics': 'I am file1.txt.'}
 
 Send a big file.  (More than 1000 bytes triggers storage to a tempfile.)
@@ -504,8 +505,8 @@
     >>> env = {'REQUEST_METHOD': 'POST',
     ...        'CONTENT_TYPE': content_type,
     ...        'wsgi.input': StringIO(input_body)}
-    >>> form = FormParser(env).parse()
-    >>> pprint.pprint(form)
+    >>> form = parse(env)
+    >>> pprint(form)
     {u'pics': <zope.httpform.parser.FileUpload object at ...>}
     >>> data = form['pics'].read()
     >>> len(data)
@@ -517,7 +518,8 @@
 ---------------
 
 What happens if you call the parse() method a second time?  It re-parses the
-WSGI/CGI environment.
+WSGI/CGI environment.  You may have to rewind the input stream for this
+to work, though.
 
     >>> env = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'x:int=1'}
     >>> p = FormParser(env)

Modified: zope.httpform/trunk/src/zope/httpform/__init__.py
===================================================================
--- zope.httpform/trunk/src/zope/httpform/__init__.py	2009-02-06 21:03:18 UTC (rev 96199)
+++ zope.httpform/trunk/src/zope/httpform/__init__.py	2009-02-07 04:42:04 UTC (rev 96200)
@@ -2,6 +2,6 @@
 """HTTP form parser that supports file uploads, Unicode, and various suffixes.
 """
 
-from zope.httpform.parser import FormParser
+from zope.httpform.parser import FormParser, parse
 
-__all__ = ('FormParser',)
+__all__ = ('FormParser', 'parse')

Modified: zope.httpform/trunk/src/zope/httpform/parser.py
===================================================================
--- zope.httpform/trunk/src/zope/httpform/parser.py	2009-02-06 21:03:18 UTC (rev 96199)
+++ zope.httpform/trunk/src/zope/httpform/parser.py	2009-02-07 04:42:04 UTC (rev 96200)
@@ -13,11 +13,10 @@
 ##############################################################################
 """HTTP form parser that supports file uploads, Unicode, and various suffixes.
 
-The FormParser class uses Python's standard ``cgi.FieldStorage`` class,
-but is easier to use than FieldStorage.  The parser converts field names
-and values to Unicode, handles file uploads in a graceful manner, and
-allows field name suffixes that tell the parser how to handle each field.
-The available suffixes are:
+The FormParser class uses Python's standard ``cgi.FieldStorage`` class.
+It converts field names and values to Unicode, handles file uploads in
+a graceful manner, and allows field name suffixes that tell the parser
+how to handle each field.  The standard suffixes are:
 
     - ``:int``      -- convert to an integer
     - ``:float``    -- convert to a float
@@ -427,3 +426,7 @@
         self.headers = field_storage.headers
         self.filename = unicode(field_storage.filename, 'UTF-8')
 
+
+def parse(env, wsgi_input=None, to_unicode=None):
+    """Shortcut for creating a FormParser and calling the parse() method."""
+    return FormParser(env, wsgi_input, to_unicode).parse()



More information about the Checkins mailing list