apetest.control
module
Models form controls.
This module contains the Control
class and its subclasses, which can be
used to model input elements in an (HTML) form.
Source code
# SPDX-License-Identifier: BSD-3-Clause
"""Models form controls.
This module contains the `Control` class and its subclasses, which can be
used to model input elements in an (HTML) form.
"""
# TODO: The subclasses currently have an almost 1:1 mapping to HTML,
# but I'm not sure that is necessary. For example SelectMultiple
# and Checkbox have the same submission functionality.
# And SelectSingle and RadioButtonGroup differ only in whether
# non-selection is possible; when adding support for the HTML5
# "required" attribute, SelectSingle can become functionally
# equivalent to RadioButtonGroup.
class Control:
"""Abstract base class for submittable elements in a form."""
def has_alternative(self, name, value):
"""Return `True` iff the given name-value combination could be
submitted by this control.
Note that for free-input controls, it is possible this method
returns `True` while the name-value pair is not in the sequence
returned by the `Control.alternatives` method.
"""
raise NotImplementedError
def alternatives(self):
"""Yield alternative name-value pairs of the ways this control
can be submitted.
For multiple-choice controls all possible alternatives are included.
For free-input controls there is an infinite number of alternatives,
so we just pick a few.
The alternative `(None, None)` represents a control not being part
of the submission, for example an unchecked checkbox.
"""
raise NotImplementedError
class SingleValueControl(Control):
"""Control that produces at most one name-value combination.
Note that there can be any number of possible values, but in each
submission of the form, no more than one value is submitted for this
control.
"""
def __init__(self, name, value):
"""Initialize control with the given name-value combination."""
Control.__init__(self)
self.name = name
"""The name under which this control is submitted."""
self.value = value
"""The default value for this control.
Some control types can only submit this value,
other control types can submit other values as well.
"""
def has_alternative(self, name, value):
return name == self.name and value == self.value
def alternatives(self):
yield self.name, self.value
class FileInput(SingleValueControl):
"""Control for selecting and uploading files."""
def has_alternative(self, name, value):
# Any text could be submitted, so we only have to check the name.
return name == self.name
def alternatives(self):
# Today's browsers, as a security precaution, will provide an empty
# file name input field even if a default value is provided.
# Since we have no idea what kind of file should be uploaded, we just
# submit the empty string.
yield self.name, ''
class HiddenInput(SingleValueControl):
"""Control that is not visible to the user.
This control submits its default value.
"""
class TextField(SingleValueControl):
"""Single-line text input."""
def has_alternative(self, name, value):
# Any text could be submitted, so we only have to check the name.
return name == self.name
def alternatives(self):
yield self.name, '' # empty
yield self.name, self.value # default
yield self.name, 'ook' # librarian's choice
class TextArea(SingleValueControl):
"""Multi-line text input."""
def has_alternative(self, name, value):
# Any text could be submitted, so we only have to check the name.
return name == self.name
def alternatives(self):
yield self.name, '' # empty
yield self.name, self.value # default
yield self.name, 'Ook.\nOok? Ook!' # librarian's choice
class Checkbox(SingleValueControl):
"""Checkbox.
This control can submit its default value (box checked)
or nothing (box unchecked).
"""
def has_alternative(self, name, value):
return (
(name is None and value is None) or
(name == self.name and value == self.value)
)
def alternatives(self):
yield None, None # box unchecked
yield self.name, self.value # box checked
class RadioButton(SingleValueControl):
"""Single radio button.
Radio buttons must be combined in a `RadioButtonGroup` control.
"""
def has_alternative(self, name, value):
assert False, 'radio button "%s" was not merged' % self.name
def alternatives(self):
assert False, 'radio button "%s" was not merged' % self.name
class RadioButtonGroup(Control):
"""Multiple-choice control containing one or more radio buttons."""
def __init__(self, buttons):
"""Initialize a radio buttons group control containing `buttons`,
which must be a non-empty sequence of `RadioButton` objects.
"""
# Perform sanity check on input and gather values.
name = buttons[0].name
values = []
for button in buttons:
if not isinstance(button, RadioButton):
raise TypeError('expected RadioButton, got %s' % type(button))
if button.name != name:
raise ValueError(
'radio button name "%s" differs from '
'first radio button name "%s"'
% (button.name, name)
)
values.append(button.value)
# Actual construction.
Control.__init__(self)
self.name = name
self.values = values
def has_alternative(self, name, value):
return name == self.name and value in self.values
def alternatives(self):
for value in self.values:
yield self.name, value
class SubmitButton(SingleValueControl):
"""Single submit button.
All submit buttons in a form must be combined in a `SubmitButtons`
control.
"""
class SubmitButtons(Control):
"""Pseudo-control which contains all submit buttons for a form.
Only one submit button can be used for submission;
this pseudo-control models the choice between submit buttons.
"""
def __init__(self, buttons):
"""Initialize a submit buttons control containing `buttons`,
which must be a sequence of `SubmitButton` objects.
"""
Control.__init__(self)
self.buttons = tuple((button.name, button.value) for button in buttons)
def has_alternative(self, name, value):
return (name, value) in self.buttons
def alternatives(self):
yield from self.buttons
class SelectMultiple(SingleValueControl):
"""Pseudo-control which represents an option in a `<select>` control
where multiple options can be active at the same time.
This type of control is typically shown in a browser as a list box.
"""
def has_alternative(self, name, value):
return (
(name is None and value is None) or
(name == self.name and value == self.value)
)
def alternatives(self):
yield None, None # not selected
yield self.name, self.value # selected
class SelectSingle(Control):
"""`<select>` control where one option can be active at the same time.
This type of control is typically shown in a browser as a drop-down list.
"""
def __init__(self, name, options):
"""Initialize a single-choice `<select>` control.
Parameters:
name
The name under which this control is submitted.
options
Sequence of all possible values for this control.
"""
Control.__init__(self)
self.name = name
self.options = tuple(options)
def has_alternative(self, name, value):
return (
(name is None and value is None) or
(name == self.name and value in self.options)
)
def alternatives(self):
yield None, None # nothing selected
for option in self.options:
yield self.name, option}
Classes
class Checkbox (ancestors: SingleValueControl, Control)
-
Checkbox.
This control can submit its default value (box checked) or nothing (box unchecked).
Source code
class Checkbox(SingleValueControl): """Checkbox. This control can submit its default value (box checked) or nothing (box unchecked). """ def has_alternative(self, name, value): return ( (name is None and value is None) or (name == self.name and value == self.value) ) def alternatives(self): yield None, None # box unchecked yield self.name, self.value # box checked}
Inherited members
class Control
-
Abstract base class for submittable elements in a form.
Source code
class Control: """Abstract base class for submittable elements in a form.""" def has_alternative(self, name, value): """Return `True` iff the given name-value combination could be submitted by this control. Note that for free-input controls, it is possible this method returns `True` while the name-value pair is not in the sequence returned by the `Control.alternatives` method. """ raise NotImplementedError def alternatives(self): """Yield alternative name-value pairs of the ways this control can be submitted. For multiple-choice controls all possible alternatives are included. For free-input controls there is an infinite number of alternatives, so we just pick a few. The alternative `(None, None)` represents a control not being part of the submission, for example an unchecked checkbox. """ raise NotImplementedError}
Subclasses
Methods
def alternatives(self)
-
Yield alternative name-value pairs of the ways this control can be submitted.
For multiple-choice controls all possible alternatives are included. For free-input controls there is an infinite number of alternatives, so we just pick a few.
The alternative
(None, None)
represents a control not being part of the submission, for example an unchecked checkbox.Source code
def alternatives(self): """Yield alternative name-value pairs of the ways this control can be submitted. For multiple-choice controls all possible alternatives are included. For free-input controls there is an infinite number of alternatives, so we just pick a few. The alternative `(None, None)` represents a control not being part of the submission, for example an unchecked checkbox. """ raise NotImplementedError}
def has_alternative(self, name, value)
-
Return
True
iff the given name-value combination could be submitted by this control.Note that for free-input controls, it is possible this method returns
True
while the name-value pair is not in the sequence returned by theControl.alternatives()
method.Source code
def has_alternative(self, name, value): """Return `True` iff the given name-value combination could be submitted by this control. Note that for free-input controls, it is possible this method returns `True` while the name-value pair is not in the sequence returned by the `Control.alternatives` method. """ raise NotImplementedError}
class FileInput (ancestors: SingleValueControl, Control)
-
Control for selecting and uploading files.
Source code
class FileInput(SingleValueControl): """Control for selecting and uploading files.""" def has_alternative(self, name, value): # Any text could be submitted, so we only have to check the name. return name == self.name def alternatives(self): # Today's browsers, as a security precaution, will provide an empty # file name input field even if a default value is provided. # Since we have no idea what kind of file should be uploaded, we just # submit the empty string. yield self.name, ''}
Inherited members
class HiddenInput (ancestors: SingleValueControl, Control)
-
Control that is not visible to the user.
This control submits its default value.
Source code
class HiddenInput(SingleValueControl): """Control that is not visible to the user. This control submits its default value. """}
Inherited members
class RadioButton (ancestors: SingleValueControl, Control)
-
Single radio button.
Radio buttons must be combined in a
RadioButtonGroup
control.Source code
class RadioButton(SingleValueControl): """Single radio button. Radio buttons must be combined in a `RadioButtonGroup` control. """ def has_alternative(self, name, value): assert False, 'radio button "%s" was not merged' % self.name def alternatives(self): assert False, 'radio button "%s" was not merged' % self.name}
Inherited members
class RadioButtonGroup (ancestors: Control)
-
Multiple-choice control containing one or more radio buttons.
Source code
class RadioButtonGroup(Control): """Multiple-choice control containing one or more radio buttons.""" def __init__(self, buttons): """Initialize a radio buttons group control containing `buttons`, which must be a non-empty sequence of `RadioButton` objects. """ # Perform sanity check on input and gather values. name = buttons[0].name values = [] for button in buttons: if not isinstance(button, RadioButton): raise TypeError('expected RadioButton, got %s' % type(button)) if button.name != name: raise ValueError( 'radio button name "%s" differs from ' 'first radio button name "%s"' % (button.name, name) ) values.append(button.value) # Actual construction. Control.__init__(self) self.name = name self.values = values def has_alternative(self, name, value): return name == self.name and value in self.values def alternatives(self): for value in self.values: yield self.name, value}
Methods
def __init__(self, buttons)
-
Initialize a radio buttons group control containing
buttons
, which must be a non-empty sequence ofRadioButton
objects.Source code
def __init__(self, buttons): """Initialize a radio buttons group control containing `buttons`, which must be a non-empty sequence of `RadioButton` objects. """ # Perform sanity check on input and gather values. name = buttons[0].name values = [] for button in buttons: if not isinstance(button, RadioButton): raise TypeError('expected RadioButton, got %s' % type(button)) if button.name != name: raise ValueError( 'radio button name "%s" differs from ' 'first radio button name "%s"' % (button.name, name) ) values.append(button.value) # Actual construction. Control.__init__(self) self.name = name self.values = values}
Inherited members
class SelectMultiple (ancestors: SingleValueControl, Control)
-
Pseudo-control which represents an option in a
<select>
control where multiple options can be active at the same time.This type of control is typically shown in a browser as a list box.
Source code
class SelectMultiple(SingleValueControl): """Pseudo-control which represents an option in a `<select>` control where multiple options can be active at the same time. This type of control is typically shown in a browser as a list box. """ def has_alternative(self, name, value): return ( (name is None and value is None) or (name == self.name and value == self.value) ) def alternatives(self): yield None, None # not selected yield self.name, self.value # selected}
Inherited members
class SelectSingle (ancestors: Control)
-
<select>
control where one option can be active at the same time.This type of control is typically shown in a browser as a drop-down list.
Source code
class SelectSingle(Control): """`<select>` control where one option can be active at the same time. This type of control is typically shown in a browser as a drop-down list. """ def __init__(self, name, options): """Initialize a single-choice `<select>` control. Parameters: name The name under which this control is submitted. options Sequence of all possible values for this control. """ Control.__init__(self) self.name = name self.options = tuple(options) def has_alternative(self, name, value): return ( (name is None and value is None) or (name == self.name and value in self.options) ) def alternatives(self): yield None, None # nothing selected for option in self.options: yield self.name, option}
Methods
def __init__(self, name, options)
-
Initialize a single-choice
<select>
control.Parameters
name
- The name under which this control is submitted.
options
- Sequence of all possible values for this control.
Source code
def __init__(self, name, options): """Initialize a single-choice `<select>` control. Parameters: name The name under which this control is submitted. options Sequence of all possible values for this control. """ Control.__init__(self) self.name = name self.options = tuple(options)}
Inherited members
class SingleValueControl (ancestors: Control)
-
Control that produces at most one name-value combination.
Note that there can be any number of possible values, but in each submission of the form, no more than one value is submitted for this control.
Source code
class SingleValueControl(Control): """Control that produces at most one name-value combination. Note that there can be any number of possible values, but in each submission of the form, no more than one value is submitted for this control. """ def __init__(self, name, value): """Initialize control with the given name-value combination.""" Control.__init__(self) self.name = name """The name under which this control is submitted.""" self.value = value """The default value for this control. Some control types can only submit this value, other control types can submit other values as well. """ def has_alternative(self, name, value): return name == self.name and value == self.value def alternatives(self): yield self.name, self.value}
Subclasses
Instance variables
var name
-
The name under which this control is submitted.
var value
-
The default value for this control.
Some control types can only submit this value, other control types can submit other values as well.
Methods
def __init__(self, name, value)
-
Initialize control with the given name-value combination.
Source code
def __init__(self, name, value): """Initialize control with the given name-value combination.""" Control.__init__(self) self.name = name """The name under which this control is submitted.""" self.value = value """The default value for this control. Some control types can only submit this value, other control types can submit other values as well. """}
Inherited members
class SubmitButton (ancestors: SingleValueControl, Control)
-
Single submit button.
All submit buttons in a form must be combined in a
SubmitButtons
control.Source code
class SubmitButton(SingleValueControl): """Single submit button. All submit buttons in a form must be combined in a `SubmitButtons` control. """}
Inherited members
class SubmitButtons (ancestors: Control)
-
Pseudo-control which contains all submit buttons for a form.
Only one submit button can be used for submission; this pseudo-control models the choice between submit buttons.
Source code
class SubmitButtons(Control): """Pseudo-control which contains all submit buttons for a form. Only one submit button can be used for submission; this pseudo-control models the choice between submit buttons. """ def __init__(self, buttons): """Initialize a submit buttons control containing `buttons`, which must be a sequence of `SubmitButton` objects. """ Control.__init__(self) self.buttons = tuple((button.name, button.value) for button in buttons) def has_alternative(self, name, value): return (name, value) in self.buttons def alternatives(self): yield from self.buttons}
Methods
def __init__(self, buttons)
-
Initialize a submit buttons control containing
buttons
, which must be a sequence ofSubmitButton
objects.Source code
def __init__(self, buttons): """Initialize a submit buttons control containing `buttons`, which must be a sequence of `SubmitButton` objects. """ Control.__init__(self) self.buttons = tuple((button.name, button.value) for button in buttons)}
Inherited members
class TextArea (ancestors: SingleValueControl, Control)
-
Multi-line text input.
Source code
class TextArea(SingleValueControl): """Multi-line text input.""" def has_alternative(self, name, value): # Any text could be submitted, so we only have to check the name. return name == self.name def alternatives(self): yield self.name, '' # empty yield self.name, self.value # default yield self.name, 'Ook.\nOok? Ook!' # librarian's choice}
Inherited members
class TextField (ancestors: SingleValueControl, Control)
-
Single-line text input.
Source code
class TextField(SingleValueControl): """Single-line text input.""" def has_alternative(self, name, value): # Any text could be submitted, so we only have to check the name. return name == self.name def alternatives(self): yield self.name, '' # empty yield self.name, self.value # default yield self.name, 'ook' # librarian's choice}
Inherited members