Form validation and rendering for Python web development.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Robust validation framework with built-in validators for common use cases and support for custom validation logic. The validation system provides data requirements, length constraints, format validation, cross-field comparisons, and extensible custom validation patterns.
Core exception classes for validation error handling.
class ValidationError(ValueError):
"""
Exception raised when validation fails.
Parameters:
- message: Error message string
"""
def __init__(self, message=""): ...
class StopValidation(Exception):
"""
Exception that stops the validation chain.
Used by validators like Optional to halt further validation.
Parameters:
- message: Optional error message
"""
def __init__(self, message=""): ...Validators that check for presence and validity of input data.
class DataRequired:
"""
Validates that field contains truthy data.
Fails on empty strings, empty lists, None, etc.
Parameters:
- message: Custom error message
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...
class InputRequired:
"""
Validates that form input was provided (even if empty).
Unlike DataRequired, accepts empty strings as valid input.
Sets 'required' flag on field.
Parameters:
- message: Custom error message
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...
class Optional:
"""
Allows empty input and stops validation chain.
If field is empty, no further validators are called.
Sets 'optional' flag on field.
Parameters:
- strip_whitespace: Whether to strip whitespace before checking (default: True)
"""
def __init__(self, strip_whitespace=True): ...
def __call__(self, form, field): ...Validators for constraining input length and numeric ranges.
class Length:
"""
Validates string length within specified bounds.
Sets 'minlength' and 'maxlength' flags on field.
Parameters:
- min: Minimum length (-1 for no minimum, default: -1)
- max: Maximum length (-1 for no maximum, default: -1)
- message: Custom error message
"""
def __init__(self, min=-1, max=-1, message=None): ...
def __call__(self, form, field): ...
class NumberRange:
"""
Validates numeric value within specified range.
Sets 'min' and 'max' flags on field.
Parameters:
- min: Minimum value (None for no minimum)
- max: Maximum value (None for no maximum)
- message: Custom error message
"""
def __init__(self, min=None, max=None, message=None): ...
def __call__(self, form, field): ...Validators for comparing field values.
class EqualTo:
"""
Compares field value to another field in the same form.
Commonly used for password confirmation fields.
Parameters:
- fieldname: Name of field to compare against
- message: Custom error message
"""
def __init__(self, fieldname, message=None): ...
def __call__(self, form, field): ...Validators for specific data formats and patterns.
class Regexp:
"""
Validates field data against regular expression pattern.
Parameters:
- regex: Regular expression pattern (string or compiled regex)
- flags: Regex flags (default: 0)
- message: Custom error message
"""
def __init__(self, regex, flags=0, message=None): ...
def __call__(self, form, field): ...
class Email:
"""
Validates email address format.
Requires 'email_validator' package for full validation.
Parameters:
- message: Custom error message
- granular_message: Whether to show specific validation errors
- check_deliverability: Whether to check if domain accepts email
- allow_smtputf8: Whether to allow international domains
- allow_empty_local: Whether to allow empty local part
"""
def __init__(self, message=None, granular_message=False,
check_deliverability=True, allow_smtputf8=True,
allow_empty_local=False): ...
def __call__(self, form, field): ...
class URL:
"""
Validates URL format.
Parameters:
- require_tld: Whether to require top-level domain (default: True)
- message: Custom error message
"""
def __init__(self, require_tld=True, message=None): ...
def __call__(self, form, field): ...
class IPAddress:
"""
Validates IP address format.
Parameters:
- ipv4: Whether to allow IPv4 addresses (default: True)
- ipv6: Whether to allow IPv6 addresses (default: False)
- message: Custom error message
"""
def __init__(self, ipv4=True, ipv6=False, message=None): ...
def __call__(self, form, field): ...
class MacAddress:
"""
Validates MAC address format.
Parameters:
- message: Custom error message
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...
class UUID:
"""
Validates UUID format.
Parameters:
- message: Custom error message
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...Validators for constraining values to specific choices.
class AnyOf:
"""
Validates that field value is in allowed list.
Parameters:
- values: Iterable of allowed values
- message: Custom error message
- values_formatter: Function to format values in error message
"""
def __init__(self, values, message=None, values_formatter=None): ...
def __call__(self, form, field): ...
class NoneOf:
"""
Validates that field value is NOT in disallowed list.
Parameters:
- values: Iterable of disallowed values
- message: Custom error message
- values_formatter: Function to format values in error message
"""
def __init__(self, values, message=None, values_formatter=None): ...
def __call__(self, form, field): ...Validators that set field state or behavior.
class ReadOnly:
"""
Marks field as read-only for display purposes.
Sets 'readonly' flag on field.
Parameters:
- message: Custom error message if field is modified
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...
class Disabled:
"""
Marks field as disabled.
Sets 'disabled' flag on field.
Parameters:
- message: Custom error message if field is modified
"""
def __init__(self, message=None): ...
def __call__(self, form, field): ...WTForms provides lowercase function aliases for all validator classes for convenience.
# Lowercase aliases for validator classes
data_required = DataRequired
input_required = InputRequired
optional = Optional
length = Length
number_range = NumberRange
equal_to = EqualTo
regexp = Regexp
email = Email
url = URL
ip_address = IPAddress
mac_address = MacAddress
any_of = AnyOf
none_of = NoneOf
readonly = ReadOnly
disabled = Disabledfrom wtforms import Form, StringField, IntegerField, validators
class UserForm(Form):
username = StringField('Username', [
validators.DataRequired(message="Username is required"),
validators.Length(min=4, max=25, message="Username must be 4-25 characters")
])
email = StringField('Email', [
validators.DataRequired(),
validators.Email(message="Invalid email address")
])
age = IntegerField('Age', [
validators.NumberRange(min=13, max=120, message="Age must be 13-120")
])
bio = StringField('Bio', [
validators.Optional(), # Field is optional
validators.Length(max=500) # But if provided, max 500 chars
])
# Validation
form = UserForm(formdata=request.form)
if form.validate():
# All fields passed validation
process_user_data(form.data)
else:
# Display errors
for field, errors in form.errors.items():
for error in errors:
print(f"{field}: {error}")class RegistrationForm(Form):
password = StringField('Password', [
validators.DataRequired(),
validators.Length(min=8, message="Password must be at least 8 characters")
])
confirm_password = StringField('Confirm Password', [
validators.DataRequired(),
validators.EqualTo('password', message='Passwords must match')
])class ContactForm(Form):
# Basic email validation
email = StringField('Email', [validators.Email()])
# Email with custom message and options
business_email = StringField('Business Email', [
validators.Email(
message="Please enter a valid business email",
check_deliverability=True, # Verify domain accepts email
granular_message=True # Show specific validation errors
)
])class ProductForm(Form):
# SKU must be format: ABC-1234
sku = StringField('SKU', [
validators.Regexp(
r'^[A-Z]{3}-\d{4}$',
message="SKU must be format ABC-1234"
)
])
# Phone number validation
phone = StringField('Phone', [
validators.Regexp(
r'^\+?1?-?\(?(\d{3})\)?-?(\d{3})-?(\d{4})$',
message="Invalid phone number format"
)
])class SurveyForm(Form):
rating = IntegerField('Rating', [
validators.AnyOf([1, 2, 3, 4, 5], message="Rating must be 1-5")
])
language = StringField('Language', [
validators.AnyOf(['en', 'es', 'fr'], message="Unsupported language")
])
# Forbidden words
comment = StringField('Comment', [
validators.NoneOf(['spam', 'test'], message="Comment contains forbidden words")
])class ServerForm(Form):
# IPv4 only
ipv4_address = StringField('IPv4 Address', [
validators.IPAddress(ipv4=True, ipv6=False)
])
# IPv6 only
ipv6_address = StringField('IPv6 Address', [
validators.IPAddress(ipv4=False, ipv6=True)
])
# Either IPv4 or IPv6
ip_address = StringField('IP Address', [
validators.IPAddress(ipv4=True, ipv6=True)
])
mac_address = StringField('MAC Address', [
validators.MacAddress()
])
server_id = StringField('Server ID', [
validators.UUID(message="Must be valid UUID")
])def validate_username_available(form, field):
"""Custom validator function."""
if User.query.filter_by(username=field.data).first():
raise ValidationError('Username already taken.')
class UsernameAvailable:
"""Custom validator class."""
def __init__(self, message=None):
self.message = message or 'Username already taken.'
def __call__(self, form, field):
if User.query.filter_by(username=field.data).first():
raise ValidationError(self.message)
class RegistrationForm(Form):
username = StringField('Username', [
validators.DataRequired(),
validate_username_available, # Function validator
UsernameAvailable() # Class validator
])class ShippingForm(Form):
same_as_billing = BooleanField('Same as billing address')
shipping_address = StringField('Shipping Address')
def validate_shipping_address(self, field):
"""Custom validation method - only required if different from billing."""
if not self.same_as_billing.data and not field.data:
raise ValidationError('Shipping address is required.')from wtforms.filters import strip_filter
def uppercase_filter(data):
"""Custom filter to uppercase data."""
return data.upper() if data else data
class ProductForm(Form):
name = StringField('Product Name',
filters=[strip_filter], # Remove leading/trailing whitespace
validators=[validators.DataRequired()]
)
code = StringField('Product Code',
filters=[strip_filter, uppercase_filter], # Strip and uppercase
validators=[validators.DataRequired()]
)
# Usage
form = ProductForm(formdata={'name': ' Widget ', 'code': ' abc123 '})
form.process()
print(form.name.data) # "Widget" (stripped)
print(form.code.data) # "ABC123" (stripped and uppercased)form = MyForm(formdata=request.form)
try:
if form.validate():
# Process valid form
save_form_data(form.data)
else:
# Handle validation errors
for field_name, errors in form.errors.items():
for error in errors:
flash(f"Error in {field_name}: {error}", 'error')
except ValidationError as e:
# Handle specific validation exception
flash(f"Validation failed: {e}", 'error')
# Access individual field errors
if form.email.errors:
email_errors = form.email.errors
print(f"Email validation failed: {', '.join(email_errors)}")class DynamicForm(Form):
email = StringField('Email')
def __init__(self, require_email=False, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add validators dynamically
if require_email:
self.email.validators = [
validators.DataRequired(),
validators.Email()
]
else:
self.email.validators = [validators.Optional()]
# Usage
form = DynamicForm(require_email=True, formdata=request.form)def validate_during_business_hours(form, field):
"""Validator that only applies during business hours."""
if datetime.now().hour < 9 or datetime.now().hour > 17:
raise ValidationError('This form can only be submitted during business hours.')
class ContactForm(Form):
message = StringField('Message', [validators.DataRequired()])
# Add extra validation at runtime
form = ContactForm(formdata=request.form)
extra_validators = {
'message': [validate_during_business_hours]
}
if form.validate(extra_validators=extra_validators):
# Process form
passInstall with Tessl CLI
npx tessl i tessl/pypi-wtforms