[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