[wwwsearch-commits] r17708 - in wwwsearch/ClientForm/trunk: .
examples
jjlee at codespeak.net
jjlee at codespeak.net
Wed Sep 21 00:25:57 CEST 2005
Author: jjlee
Date: Wed Sep 21 00:25:56 2005
New Revision: 17708
Modified:
wwwsearch/ClientForm/trunk/ClientForm.py
wwwsearch/ClientForm/trunk/README.html.in
wwwsearch/ClientForm/trunk/examples/example.py
wwwsearch/ClientForm/trunk/test.py
Log:
Apply Gary's patch adding more tests and docs for his new features; Bugfix and misc minor changes related to new features
Modified: wwwsearch/ClientForm/trunk/ClientForm.py
==============================================================================
--- wwwsearch/ClientForm/trunk/ClientForm.py (original)
+++ wwwsearch/ClientForm/trunk/ClientForm.py Wed Sep 21 00:25:56 2005
@@ -25,7 +25,7 @@
"""
# XXX
-# Add some functional tests
+# Add some more functional tests
# Especially single and multiple file upload on the internet.
# Does file upload work when name is missing? Sourceforge tracker form
# doesn't like it. Check standards, and test with Apache. Test
@@ -34,7 +34,7 @@
# boundaries, so file uploads may fail with those servers. Should
# copy what IE does religiously.
# Unicode: see Wichert Akkerman's 2004-01-22 message to c.l.py.
-# Controls can have name=None (eg. forms constructed partly with
+# Controls can have name=None (e.g. forms constructed partly with
# JavaScript), but find_control can't be told to find a control
# with that name, because None there means 'unspecified'. Can still
# get at by nr, but would be nice to be able to specify something
@@ -801,7 +801,7 @@
name = d.get("name")
# we don't want to lose information, so use a type string that
# doesn't clash with INPUT TYPE={SUBMIT,RESET,BUTTON}
- # eg. type for BUTTON/RESET is "resetbutton"
+ # e.g. type for BUTTON/RESET is "resetbutton"
# (type for INPUT/RESET is "reset")
type = type+"button"
self._add_label(d)
@@ -853,7 +853,7 @@
except AttributeError:
escaped_attrs[key] = self.unescape_attr(val)
else:
- # eg. "__select" -- yuck!
+ # e.g. "__select" -- yuck!
escaped_attrs[key] = self.unescape_attrs(val)
return escaped_attrs
@@ -953,7 +953,7 @@
ignore_errors=False, # ignored!
form_parser_class=FormParser,
request_class=urllib2.Request,
- entitydefs=None):
+ entitydefs=None, ignore_ambiguity=True):
"""Parse HTTP response and return a list of HTMLForm instances.
The return value of urllib2.urlopen can be conveniently passed to this
@@ -970,6 +970,12 @@
urllib2.Request)
entitydefs: mapping like {'&': '&', ...} containing HTML entity
definitions (a sensible default is used)
+ ignore_ambiguity: boolean that determines how the form's label searches
+ will be performed. If ignore_ambiguity is True, label searches that do
+ not specify a nr (number or count) will always get the first match, even
+ if other controls match. This is legacy behavior, and will be deprecated
+ in a future release. If ignore_ambiguity is False, label searches that
+ have ambiguous results will raise an AmbiguityError.
Pass a true value for select_default if you want the behaviour specified by
RFC 1866 (the HTML 2.0 standard), which is to select the first item in a
@@ -993,13 +999,13 @@
False,
form_parser_class,
request_class,
- entitydefs)
+ entitydefs, ignore_ambiguity)
def ParseFile(file, base_uri, select_default=False,
ignore_errors=False, # ignored!
form_parser_class=FormParser,
request_class=urllib2.Request,
- entitydefs=None):
+ entitydefs=None, ignore_ambiguity=True):
"""Parse HTML and return a list of HTMLForm instances.
ClientForm.ParseError is raised on parse errors.
@@ -1048,7 +1054,7 @@
# would be nice to make HTMLForm class (form builder) pluggable
form = HTMLForm(
action, method, enctype, name, attrs, request_class,
- forms, labels, id_to_labels)
+ forms, labels, id_to_labels, ignore_ambiguity)
for type, name, attrs in controls:
attrs = fp.unescape_attrs_if_required(attrs)
name = fp.unescape_attr_if_required(name)
@@ -1550,6 +1556,8 @@
" ".join(['%s=%r' % (k, v) for k, v in attrs])
)
+# XXX sort this out (and assertions about items and controls being in exactly
+# one container)
# how to remove items from a list container: delete them as usual
# ("del control.items[:]", for instance).
# how to add items to a list container: instantiate Item with control, and add
@@ -1714,7 +1722,8 @@
nr is an optional 0-based index of the items matching the query.
If nr is the default None value and more than item is found, raises
- AmbiguityError.
+ AmbiguityError (unless the HTMLForm constructor was passed a true
+ ignore_ambiguity argument).
If no item is found, raises ItemNotFoundError.
@@ -1728,6 +1737,8 @@
method = self.items_from_label
else:
method = self.items_from_name
+ if nr is None and self._form.ignore_ambiguity:
+ nr = 0 # :-/
return disambiguate(method(name, exclude_disabled), nr, name)
def toggle(self, name, by_label=False, nr=None):
@@ -1777,7 +1788,7 @@
item.__dict__['_selected'] = action
else:
if not action:
- item.__dict__['_selected'] = action
+ item.__dict__['_selected'] = False
else:
for o in self.items:
o.__dict__['_selected'] = False
@@ -1906,7 +1917,7 @@
for o in self.items:
# set items' controls to self, now that we've merged
- o.__dict__['control'] = self
+ o.__dict__['_control'] = self
def __getattr__(self, name):
if name == "value":
@@ -1997,7 +2008,9 @@
value is expected to be an iterable of strings that are substrings of
the item labels that should be selected. Ambiguous labels are accepted
- as long as all ambiguous labels share the same item name.
+ without complaint if the form's ignore_ambiguity is True; otherwise,
+ it will not complain as long as all ambiguous labels share the same
+ item name (e.g. OPTION value).
"""
if isstringlike(value):
@@ -2006,11 +2019,14 @@
for nn in value:
found = self.items_from_label(nn)
if len(found) > 1:
- # ambiguous labels are fine as long as item names (eg. OPTION
- # values) are same
- opt_name = found[0].name
- if [o for o in found[1:] if o.name != opt_name]:
- raise AmbiguityError(nn)
+ if not self._form.ignore_ambiguity:
+ # ambiguous labels are fine as long as item names (e.g.
+ # OPTION values) are same
+ opt_name = found[0].name
+ if [o for o in found[1:] if o.name != opt_name]:
+ raise AmbiguityError(nn)
+ # else: OK, we'll guess :-(
+ # XXXX is this guess consistent with 0.1??
for o in found:
# For the multiple-item case, we could try to be smarter,
# saving them up and trying to resolve, but that's too much.
@@ -2574,7 +2590,8 @@
enctype="application/x-www-form-urlencoded",
name=None, attrs=None,
request_class=urllib2.Request,
- forms=None, labels=None, id_to_labels=None):
+ forms=None, labels=None, id_to_labels=None,
+ ignore_ambiguity=True):
"""
In the usual case, use ParseResponse (or ParseFile) to create new
HTMLForm objects.
@@ -2599,6 +2616,7 @@
self._forms = forms # this is a semi-public API!
self._labels = labels # this is a semi-public API!
self._id_to_labels = id_to_labels # this is a semi-public API!
+ self.ignore_ambiguity = ignore_ambiguity
def new_control(self, type, name, attrs,
ignore_unknown=False, select_default=False):
@@ -2953,6 +2971,8 @@
other arguments (if supplied); it is not necessarily the first control
in the form.
+ If label is specified, then the control must have this label. Note
+ that radio controls and checkboxes never have labels: their items do.
"""
if ((name is None) and (type is None) and (kind is None) and
(id is None) and (label is None) and (predicate is None) and
Modified: wwwsearch/ClientForm/trunk/README.html.in
==============================================================================
--- wwwsearch/ClientForm/trunk/README.html.in (original)
+++ wwwsearch/ClientForm/trunk/README.html.in Wed Sep 21 00:25:56 2005
@@ -49,7 +49,9 @@
response = urlopen(form.click("Thanks"))
""")}
-<p>A more complicated example:
+<p>A more complicated example (<em><strong>Note</strong>: this example makes
+use of the ClientForm 0.2 API; refer to the README.html file in the latest 0.1
+release for the corresponding code for that version.</em>):
@{colorize(" "+" ".join(open("examples/example.py").readlines()[2:]))}
@@ -91,20 +93,17 @@
<p>For installation instructions, see the INSTALL file included in the
distribution.
-<p><em>Development release.</em>. There have been XXX two? minor
-backwards-incompatible interface changes since 0.1.x. I recommend upgrading if
-and only if you pay attention to these incompatible changes:
-
-<ul>
- <li>Disabled list items may no longer be deselected: AttributeError is raised
- in 0.2.x, whereas deselection was allowed in 0.1.x. In 0.2.x, either simply
- set <code>item.disabled = False</code> first (or call
- <code>control.set_all_items_disabled(False)</code>), or check if the item is
- disabled before attempting to select or deselect it. Note also that handling
- of disabled list items in 0.1.x was buggy: disabled items were successful
- (ie. disabled item names got sent back to the server).</li>
- <li>AmbiguityError?</li>
-</ul>
+<p><em>Development release.</em>. There has been one minor
+backwards-incompatible interface change since 0.1.x. I recommend upgrading if
+and only if you pay attention to this incompatible change:
+
+<p>Disabled list items may no longer be deselected: AttributeError is raised
+in 0.2.x, whereas deselection was allowed in 0.1.x. In 0.2.x, either simply
+set <code>item.disabled = False</code> first (or call
+<code>control.set_all_items_disabled(False)</code>), or check if the item is
+disabled before attempting to select or deselect it. Note also that handling
+of disabled list items in 0.1.x was buggy: disabled items were successful
+(ie. disabled item names got sent back to the server).
<p>0.2.x includes better support for labels, and a redesigned,
simpler, interface (all the old methods are still there. but some have
Modified: wwwsearch/ClientForm/trunk/examples/example.py
==============================================================================
--- wwwsearch/ClientForm/trunk/examples/example.py (original)
+++ wwwsearch/ClientForm/trunk/examples/example.py Wed Sep 21 00:25:56 2005
@@ -5,10 +5,11 @@
request = urllib2.Request(
"http://wwwsearch.sourceforge.net/ClientForm/example.html")
response = urllib2.urlopen(request)
-forms = ClientForm.ParseResponse(response)
+forms = ClientForm.ParseResponse(response, ignore_ambiguity=False)
response.close()
## f = open("example.html")
-## forms = ClientForm.ParseFile(f, "http://example.com/example.html")
+## forms = ClientForm.ParseFile(f, "http://example.com/example.html",
+## ignore_ambiguity=False)
## f.close()
form = forms[0]
print form # very useful!
@@ -76,7 +77,6 @@
# easier to maintain than names, sometimes the other way around.
form.set_value(["Mozzarella", "Caerphilly"], "cheeses", by_label=True)
# is the "parmesan" item of the "cheeses" control selected?
-print 1
print "parmesan" in form["cheeses"]
# does cheeses control have a "caerphilly" item?
print "caerphilly" in [item.name for item in form.find_control("cheeses").items]
Modified: wwwsearch/ClientForm/trunk/test.py
==============================================================================
--- wwwsearch/ClientForm/trunk/test.py (original)
+++ wwwsearch/ClientForm/trunk/test.py Wed Sep 21 00:25:56 2005
@@ -58,6 +58,11 @@
self._forms = []
self._labels = []
self._id_to_labels = {}
+ self.ignore_ambiguity = True
+ self.controls = []
+
+ def find_control(self, name, type):
+ raise ClientForm.ControlNotFoundError
class UnescapeTests(TestCase):
def test_unescape(self):
@@ -1068,7 +1073,9 @@
"name": "name_value",
"value": "value_value",
"alt": "some string"}
+ form = DummyForm()
c = ClientForm.CheckboxControl("checkbox", "name_value", attrs)
+ c.add_to_form(form)
c.fixup()
self.assert_(c.type == "checkbox")
self.assert_(c.name == "name_value")
@@ -1088,7 +1095,9 @@
attrs2 = attrs.copy()
attrs2["value"] = "value_value2"
c2 = ClientForm.CheckboxControl("checkbox", "name_value", attrs2)
+ c2.add_to_form(form)
c.merge_control(c2)
+ c.add_to_form(form)
c.fixup()
self.assert_(str(c) == "<CheckboxControl("
"name_value=[value_value, value_value2])>")
@@ -1185,8 +1194,10 @@
"name": "select_name",
"multiple": "",
"alt": "alt_text"}}
+ form = DummyForm()
# with Netscape / IE default selection...
c = ClientForm.SelectControl("select", "select_name", attrs)
+ c.add_to_form(form)
c.fixup()
self.assert_(c.type == "select")
self.assert_(c.name == "select_name")
@@ -1199,6 +1210,7 @@
self.assert_(c.attrs["alt"] == "alt_text")
# ... and with RFC 1866 default selection
c = ClientForm.SelectControl("select", "select_name", attrs, select_default=True)
+ c.add_to_form(form)
c.fixup()
self.assert_(c.value == ["value_value"])
@@ -1207,7 +1219,9 @@
attrs2 = attrs.copy()
attrs2["value"] = "value_value2"
c2 = ClientForm.SelectControl("select", "select_name", attrs2)
+ c2.add_to_form(form)
c.merge_control(c2)
+ c.add_to_form(form)
c.fixup()
self.assert_(str(c) == "<SelectControl("
"select_name=[value_value, value_value2])>")
@@ -1309,9 +1323,10 @@
c = ClientForm.SelectControl("select", "select_name", attrs)
c2 = ClientForm.SelectControl("select", "select_name", attrs2)
c3 = ClientForm.SelectControl("select", "select_name", attrs3)
- c._form = DummyForm()
+ form = DummyForm()
c.merge_control(c2)
c.merge_control(c3)
+ c.add_to_form(form)
c.fixup()
hide_deprecations()
@@ -1412,9 +1427,10 @@
c = ClientForm.SelectControl("select", "select_name", attrs)
c2 = ClientForm.SelectControl("select", "select_name", attrs2)
c3 = ClientForm.SelectControl("select", "select_name", attrs3)
- c._form = DummyForm()
+ form = DummyForm()
c.merge_control(c2)
c.merge_control(c3)
+ c.add_to_form(form)
c.fixup()
hide_deprecations()
@@ -1460,6 +1476,8 @@
"alt": "alt_text"}}
# Netscape and IE behaviour...
c = ClientForm.SelectControl("select", "select_name", attrs)
+ form = DummyForm()
+ c.add_to_form(form)
c.fixup()
self.assert_(c.type == "select")
self.assert_(c.name == "select_name")
@@ -1473,6 +1491,7 @@
# ...and RFC 1866 behaviour are identical (unlike multiple SELECT).
c = ClientForm.SelectControl("select", "select_name", attrs,
select_default=1)
+ c.add_to_form(form)
c.fixup()
self.assert_(c.value == ["value_value"])
@@ -1482,6 +1501,7 @@
attrs2["value"] = "value_value2"
c2 = ClientForm.SelectControl("select", "select_name", attrs2)
c.merge_control(c2)
+ c.add_to_form(form)
c.fixup()
self.assert_(str(c) == "<SelectControl("
"select_name=[*value_value, value_value2])>")
@@ -1555,6 +1575,8 @@
"id": "blah"}
# Netscape and IE behaviour...
c = ClientForm.RadioControl("radio", "name_value", attrs)
+ form = DummyForm()
+ c.add_to_form(form)
c.fixup()
self.assert_(c.type == "radio")
self.assert_(c.name == "name_value")
@@ -1566,6 +1588,7 @@
# ...and RFC 1866 behaviour
c = ClientForm.RadioControl("radio", "name_value", attrs,
select_default=True)
+ c.add_to_form(form)
c.fixup()
self.assert_(c.value == ["value_value"])
@@ -1577,6 +1600,7 @@
c2 = ClientForm.RadioControl("radio", "name_value", attrs2,
select_default=True)
c.merge_control(c2)
+ c.add_to_form(form)
c.fixup()
self.assert_(str(c) == "<RadioControl("
"name_value=[*value_value, value_value2])>")
@@ -1588,47 +1612,47 @@
self.assertRaises(ItemCountError, set_value,
["value_value", "value_value2"])
self.assertRaises(TypeError, set_value, "value_value")
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
c.value = ["value_value2"]
- self.assert_(c.value == ["value_value2"])
+ self.assertEqual(c.value, ["value_value2"])
c.value = ["value_value"]
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
self.assertRaises(ItemNotFoundError, set_value, ["oops"])
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
hide_deprecations()
c.toggle("value_value")
- self.assert_(c.value == [])
+ self.assertEqual(c.value, [])
c.toggle("value_value")
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
self.assertRaises(TypeError, c.toggle, ["value_value"])
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
# nothing selected is allowed
c.value = []
- self.assert_(c.value == [])
+ self.assertEqual(c.value, [])
c.set(True, "value_value")
reset_deprecations()
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
c.readonly = True
self.assertRaises(AttributeError, c.clear)
c.readonly = False
c.clear()
- self.assert_(c.value == [])
+ self.assertEqual(c.value, [])
# set
hide_deprecations()
c.set(True, "value_value")
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
c.set(True, "value_value")
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
c.set(True, "value_value2")
- self.assert_(c.value == ["value_value2"])
+ self.assertEqual(c.value, ["value_value2"])
c.set(False, "value_value")
self.assert_("value_value2")
c.set(False, "value_value2")
- self.assert_(c.value == [])
+ self.assertEqual(c.value, [])
c.set(False, "value_value2")
- self.assert_(c.value == [])
+ self.assertEqual(c.value, [])
self.assertRaises(ItemNotFoundError, c.set, True, "oops")
self.assertRaises(TypeError, c.set, True, ["value_value"])
self.assertRaises(ItemNotFoundError, c.set, False, "oops")
@@ -1654,9 +1678,9 @@
"__label": {"__text": "Third Option"}}
c3 = ClientForm.RadioControl("radio", "name_value", attrs)
form = DummyForm()
- c1._form = form
c1.merge_control(c2)
c1.merge_control(c3)
+ c1.add_to_form(form)
c1.fixup()
self.assertEqual(c1.value, ['value_value'])
hide_deprecations()
@@ -2004,11 +2028,41 @@
<td><input type="text" id="form.password" name="form.password"
value="123" /></tr>
</tr>
+ <tr>
+ <td>In this grocery list of requested food items, mark the items you intend
+ to purchase:
+ </td>
+ <td>
+ <label><input type="checkbox" name="form.grocery" value="bread"/>
+ Loaf of Bread</label> |
+ <label><input type="checkbox" name="form.grocery" value="bread"/>
+ Loaf of Bread</label> |
+ <label><input type="checkbox" name="form.grocery" value="bread"/>
+ Loaf of Bread</label> |
+ <label><input type="checkbox" name="form.grocery" value="challah"/>
+ Loaf of Challah</label> |
+ <label><input type="checkbox" name="form.grocery" value="eggs"/>
+ Dozen Eggs</label> |
+ <label><input type="checkbox" name="form.grocery" value="milk"/>
+ Half-Gallon of Milk</label> |
+ <label><input type="checkbox" name="form.grocery" value="milk"/>
+ Half-Gallon of Milk</label> |
+ <label><input type="checkbox" name="form.grocery" value="diapers"/>
+ 36 30lb. Diapers</label> |
+ <label><input type="checkbox" name="form.grocery" value="diapers"/>
+ 36 30lb. Diapers</label> |
+ <label><input type="checkbox" name="form.grocery" value="diapers"/>
+ 36 30lb. Diapers</label> |
+ <label><input type="checkbox" name="form.grocery" value="diapers"/>
+ 36 30lb. Diapers</label>
+ </td>
</table>
<input type="submit" value="Submit" />
</form>
""")
form = ClientForm.ParseFile(f, "http://localhost/")[0]
+
+ # basic tests
self.assertEqual(form.find_control(label='Title').value,
'The Grapes of Wrath')
self.assertEqual(form.find_control(label='Submit').value,
@@ -2024,6 +2078,56 @@
self.assertEqual(form.find_control(label='Title').value,
'The Grapes of Wrath')
+ # test item ambiguity, get, items_from_label, items_from_name, and
+ # set_value_by_label
+ # a form can be in two states: either ignoring ambiguity or being
+ # careful about it. Currently, by default, a form's ignore_ambiguity
+ # attribute is True, so ambiguity is ignored. For instance, notice
+ # that the form.grocery checkboxes include some loaves of bread and
+ # a loaf of challah. The code just guesses what you mean:
+ c = form.find_control('form.grocery')
+ self.assertEqual(c.get('Loaf', True), c.items[0])
+ c.set_value_by_label(['Loaf'])
+ self.assertEqual(c.get_value_by_label(), ['Loaf of Bread'])
+ # However, if the form's ignore_ambiguity attribute is False, Ambiguity
+ # Errors may be raised. This is generally a preferred approach, but is
+ # not backwards compatible.
+ form.ignore_ambiguity = False
+ self.assertRaises(ClientForm.AmbiguityError, c.get, 'Loaf', True)
+ self.assertRaises(
+ ClientForm.AmbiguityError, c.set_value_by_label, ['Loaf'])
+ # If items have the same name (value), set_value_by_label will
+ # be happy (since it is just setting the value anyway).
+ c.set_value_by_label(['Loaf of Bread'])
+ self.assertEqual(c.get_value_by_label(), ['Loaf of Bread'])
+ c.set_value_by_label(
+ ['Loaf of Bread', 'Loaf of Bread', 'Loaf of Challah'])
+ self.assertEqual(
+ c.get_value_by_label(),
+ ['Loaf of Bread', 'Loaf of Bread', 'Loaf of Challah'])
+ # "get" will still raise an exception, though.
+ self.assertRaises(
+ ClientForm.AmbiguityError, c.get, 'Loaf of Bread', True)
+ # If you want an item, you need to
+ # specify which one you want (or use items_from_label or
+ # items_from_name to explicitly get all of them).
+ self.assertEqual(c.get('Loaf of Bread', True, 0).selected, True)
+ self.assertEqual(c.get('Loaf of Bread', True, 1).selected, True)
+ self.assertEqual(c.get('Loaf of Bread', True, 2).selected, False)
+ self.assertEqual(c.get('Loaf of Challah', True).selected, True)
+ self.assertEqual(
+ [i.selected for i in c.items_from_label('Loaf of Bread')],
+ [True, True, False])
+ self.assertEqual(
+ [i.selected for i in c.items_from_label('Loaf of Challah')],
+ [True])
+ self.assertEqual(
+ [i.name for i in c.items_from_label('Loaf')],
+ ['bread', 'bread', 'bread', 'challah'])
+ self.assertEqual(
+ [i.get_labels()[0].text for i in c.items_from_name('bread')],
+ ['Loaf of Bread', 'Loaf of Bread', 'Loaf of Bread'])
+
# test deprecation
if warnings_imported:
try:
@@ -2039,42 +2143,36 @@
pass
else:
self.fail('deprecation failed')
- # warnings are nasty. :-(
try:
c.toggle_single()
except DeprecationWarning:
pass
else:
self.fail('deprecation failed')
- # warnings are nasty. :-(
try:
c.set_single(True)
except DeprecationWarning:
pass
else:
self.fail('deprecation failed')
- # warnings are nasty. :-(
try:
c.toggle(f)
except DeprecationWarning:
pass
else:
self.fail('deprecation failed')
- # warnings are nasty. :-(
try:
c.get_item_disabled(f)
except DeprecationWarning:
pass
else:
self.fail('deprecation failed')
- # warnings are nasty. :-(
try:
c.set_item_disabled(True, f)
except DeprecationWarning:
pass
else:
self.fail('deprecation failed')
- # warnings are nasty. :-(
try:
c.get_item_attrs(True, f)
except DeprecationWarning:
More information about the wwwsearch-commits
mailing list