Streamlit Part 3: Form Validation
Form Validation Part 1
A Roundhouse Kick into Streamlit Form Validation
Amid the rhythmic thuds of gloves hitting pads, Rick and Chris were immersed in their kickboxing class. Between combos, they exchanged thoughts—not just on perfecting their strikes but also on coding challenges. As they caught their breath, the conversation shifted to Streamlit and the importance of form validation.
Rick: Panting "You know, Chris, it's like the saying 'garbage in, garbage out.' If I don't validate the data properly in my Streamlit app, I can't expect good results. I need to guard the gate and make sure only clean data gets through."
Chris: Laughs "Nice analogy! Just as a misstep here could land you on the mat, bad input can crash your app. What are you working on?"
Rick: "I'm building an app that collects user information, and I want to ensure the data is valid before processing it. Any tips on implementing form validation in Streamlit?"
Chris: "Absolutely! Streamlit doesn't have built-in validation like some web frameworks, but you can create custom validation functions. It's like customizing your training routine."
Rick: "Sounds good. Maybe after class, you can guide me through setting it up?"
Chris: "Sure thing. But remember, no coding while kicking—you might accidentally roundhouse your laptop!"
They both chuckled, resuming their training with renewed energy.
The original article appears here at the Technologist: Article 3 Streamlits.
Implementing Form Validation in Streamlit
In this tutorial, we'll explore how to implement form validation in a Streamlit application. Just as proper form is crucial in kickboxing, validating user input ensures your app runs smoothly and securely.
We'll break down the implementation into manageable steps. The great thing about Streamlits is the code is simple to understand without any complex component wiring or fancy callbacks. It is very easy to read and edit.
In this article, we'll dive deep into form handling and validation in Streamlit applications. Streamlit, a popular Python library for creating web applications, offers a straightforward approach to building interactive forms. However, it doesn't provide built-in form validation, which is crucial for ensuring data integrity and enhancing user experience.
Here's an overview of the key concepts and steps we'll cover:
By the end of this tutorial, you'll have a comprehensive understanding of how to implement robust form validation in your Streamlit applications, combining the simplicity of Streamlit with the power of custom Python validation logic.
Let's outline the key steps we'll cover in implementing form validation in Streamlit:
By following these steps, we'll create a robust, user-friendly form with comprehensive validation in Streamlit.
Setting Up the Project
First, let's set up the project environment and install the required packages.
Create and activate a virtual environment:
# Create and activate virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\\\\Scripts\\\\activate
Install Streamlit:
pip install streamlit
Create the application file:
touch form_validation.py
Step-by-Step Guide to Form Validation
1. Import Required Libraries
import streamlit as st
import re
from datetime import datetime, date, timedelta
These imports provide us with the necessary functions for handling dates, regular expressions, and Streamlit components.
2. Define Validation Functions
Creating separate functions for each validation makes the code modular and reusable.
2.1 Name Validation
def validate_name(name, field_name="Name"):
if not name:
return False, f"{field_name} is required"
if not re.match(r'^[A-Za-z\\\\s-]+$', name):
return False, f"{field_name} should only contain letters, spaces, and hyphens"
if len(name) < 2:
return False, f"{field_name} should be at least 2 characters long"
return True, ""
This function, validate_name(), is designed to validate a name input. Here's a detailed breakdown of its functionality:
2.2 Email Validation
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return False, "Please enter a valid email address"
return True, ""
Purpose: Validates the email format using a regular expression.
The email validation function uses a regular expression pattern to check if the provided email address is in a valid format. Here's a breakdown of the pattern:
If the email doesn't match this pattern, the function returns False with an error message. Otherwise, it returns True with an empty string, indicating a valid email format.
2.3 Phone Validation
def validate_phone(phone):
pattern = r'^\\\\(?([0-9]{3})\\\\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$'
if not re.match(pattern, phone):
return False, "Please enter a valid phone number"
return True, ""
The code above implements phone number validation using a regular expression pattern. Here's a detailed explanation of the regex pattern used:
This pattern allows for flexibility in phone number formats while ensuring the basic structure of a 10-digit U.S. phone number is maintained.
2.4 Age Validation
def validate_age(age):
try:
age = int(age)
if age < 0 or age > 120:
return False, "Age must be between 0 and 120"
return True, ""
except ValueError:
return False, "Please enter a valid number"
Purpose: Ensures the age is a valid integer between 0 and 120.
The validate_age() function is designed to ensure that the age entered by the user is valid. Here's a breakdown of its functionality:
This function ensures that the age input is both numeric and within a reasonable range, providing appropriate feedback for invalid entries.
2.5 Date Validation
def validate_date(input_date, earliest_date=date(1990, 1, 1)):
max_future_date = date.today() + timedelta(days=5 * 365)
if input_date < earliest_date:
return False, f"Date must be after {earliest_date.strftime('%Y-%m-%d')}"
elif input_date > max_future_date:
return False, f"Date cannot be more than 5 years in the future"
return True, ""
Purpose: Validates that the date is within a reasonable range.
Example: Birth dates after 1990 and not more than five years into the future.
The validate_date() function is designed to ensure that a given date falls within an acceptable range. Here's a detailed breakdown of its functionality:
This function ensures that dates entered by users are within a reasonable range, preventing issues with unrealistic past or future dates.
3. Initialize Session State
if 'form_data' not in st.session_state:
st.session_state.form_data = {
'first_name': '',
'last_name': '',
'email': '',
'phone': '',
'age': '',
'birth_date': date.today(),
'start_date': date.today(),
'end_date': date.today() + timedelta(days=1),
'submitted': False
}
Purpose: Stores form data persistently across reruns.
Why: Streamlit apps rerun on interactions; session state maintains the data.
This code snippet demonstrates the initialization of session state in a Streamlit application. Here's a breakdown of its functionality:
This initialization ensures that the form always has a consistent starting state, whether it's the user's first visit or a return to the page after navigation.
4. Create the Form Layout
4.1 Personal Information Section
with st.form("enhanced_validation"):
st.header("?? Personal Information")
col1, col2 = st.columns(2)
with col1:
first_name = st.text_input(
"First Name",
value=st.session_state.form_data['first_name']
)
# Real-time validation
if first_name:
is_valid, message = validate_name(first_name, "First name")
if not is_valid:
st.error(message)
else:
st.success("Valid first name!")
Explanation: Uses columns to place first and last name side by side.
Real-time Validation: Provides immediate feedback as the user types.
This code snippet demonstrates the creation of a form layout in Streamlit, focusing on the personal information section. Here's a breakdown of its key components:
This approach provides a user-friendly interface with instant feedback, enhancing the overall form-filling experience.
4.2 Contact Information
st.header("?? Contact Information")
email = st.text_input(
"Email",
value=st.session_state.form_data['email']
)
if email:
is_valid, message = validate_email(email)
if not is_valid:
st.error(message)
else:
st.success("Valid email format!")
There's no special wiring required—the email validation code is straightforward and intuitive. It's both easy to write and easy to understand.
Purpose: Collects email and validates it on input.
The code snippet above demonstrates the implementation of email validation in a Streamlit form. Here's a detailed breakdown:
This approach provides immediate feedback to the user, enhancing the user experience by catching and displaying errors in real-time, rather than waiting for form submission.
4.3 Age and Birth Date
st.header("?? Personal Details")
col3, col4 = st.columns(2)
with col3:
age = st.number_input(
"Age",
min_value=0,
max_value=120,
value=0 if not st.session_state.form_data['age'] else int(st.session_state.form_data['age'])
)
if age:
is_valid, message = validate_age(age)
if not is_valid:
st.error(message)
else:
st.success("Valid age!")
Explanation: Ensures age is within a realistic range.
There's no special wiring required—the age validation code is straightforward and intuitive. It's both easy to write and easy to understand.
Behold, the age validation code so slick, it could slide into your form's DMs without breaking a sweat! It's so straightforward, it makes a straight line look like a pretzel. This code is the smooth operator of the validation world - James Bond in digital form, if you will.
It's like the Swiss Army knife of age checking - compact, efficient, and ready to tackle any age-related mischief faster than you can say "I swear I'm old enough to rent this car!" This validation is so intuitive, it practically reads your mind. It's the kind of code that makes other validators green with envy.
The point is, there's no special wiring required. It's just simple, easy-to-read Python code.
5. Implement Form Submission
submitted = st.form_submit_button("Submit Form")
if submitted:
# Validate all fields
validations = [
validate_name(first_name, "First name"),
validate_name(last_name, "Last name"),
validate_email(email),
validate_phone(phone),
validate_age(age),
validate_date(birth_date),
(start_date < end_date, "Start date must be before end date")
]
# Check if all validations pass
if all(v[0] for v in validations):
st.success("Form submitted successfully!")
# Update session state
st.session_state.form_data.update({
'first_name': first_name,
'last_name': last_name,
'email': email,
'phone': phone,
'age': age,
'birth_date': birth_date,
'start_date': start_date,
'end_date': end_date,
'submitted': True
})
st.rerun()
else:
# Show all validation errors
for valid, message in validations:
if not valid:
st.error(message)
Explanation: Upon form submission, all fields undergo comprehensive validation.
Success: If all validations pass, the app updates the session state and reruns.
Errors: The user sees a clear display of any validation errors.
Let's break down the code step by step, similar to how we explained it for the consumer:
This structure provides a comprehensive, user-friendly form with real-time validation, persistent data storage, and a clear summary display, enhancing the overall user experience.
6. Display Submission Summary in Sidebar
if st.session_state.form_data['submitted']:
if create_sidebar_markdown(st.session_state.form_data):
# Clear form if button clicked
for key in st.session_state.form_data:
if key in ['start_date', 'end_date', 'birth_date']:
st.session_state.form_data[key] = date.today()
elif key == 'submitted':
st.session_state.form_data[key] = False
else:
st.session_state.form_data[key] = ''
st.rerun()
7. Create Sidebar Summary Function
def create_sidebar_markdown(form_data):
"""Create a markdown summary of form data for the sidebar."""
st.sidebar.markdown("---")
st.sidebar.title("? Submission Summary")
# Personal Details Section
st.sidebar.header(f"?? {form_data['first_name']} {form_data['last_name']}")
# Contact Information
st.sidebar.subheader("?? Contact Details")
st.sidebar.markdown(f"""
- ?? {form_data['email']}
- ?? {form_data['phone']}
""")
# Personal Information
st.sidebar.subheader("?? Personal Info")
st.sidebar.markdown(f"""
- ?? Age: {form_data['age']}
- ?? Birth Date: {form_data['birth_date'].strftime('%Y-%m-%d')}
""")
# Schedule Information
st.sidebar.subheader("?? Schedule")
st.sidebar.markdown(f"""
| Date Type | Value |
|-----------|-------|
| Start | {form_data['start_date'].strftime('%Y-%m-%d')} |
| End | {form_data['end_date'].strftime('%Y-%m-%d')} |
""")
# Submission Details
st.sidebar.markdown("---")
st.sidebar.markdown(f"*Submitted: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
# Add Clear Form Button to Sidebar
if st.sidebar.button("?? Clear Form"):
return True
return False
Explanation: Formats the submitted data into a readable summary using Markdown.
Features: Includes personal details, contact information, schedule, and a timestamp.
The above is not really part of form handling per se. It is just there so you can see what is going on with the form validation.
Full Code Listing
Here's the complete code for the form validation application.
import streamlit as st
from datetime import datetime, date, timedelta
import re
def create_sidebar_markdown(form_data):
"""Create a markdown summary of form data for the sidebar."""
st.sidebar.markdown("---")
st.sidebar.title("? Submission Summary")
# Personal Details Section
st.sidebar.header(f"?? {form_data['first_name']} {form_data['last_name']}")
# Contact Information
st.sidebar.subheader("?? Contact Details")
st.sidebar.markdown(f"""
- ?? {form_data['email']}
- ?? {form_data['phone']}
""")
# Personal Information
st.sidebar.subheader("?? Personal Info")
st.sidebar.markdown(f"""
- ?? Age: {form_data['age']}
- ?? Birth Date: {form_data['birth_date'].strftime('%Y-%m-%d')}
""")
# Schedule Information
st.sidebar.subheader("?? Schedule")
st.sidebar.markdown(f"""
| Date Type | Value |
|-----------|-------|
| Start | {form_data['start_date'].strftime('%Y-%m-%d')} |
| End | {form_data['end_date'].strftime('%Y-%m-%d')} |
""")
# Submission Details
st.sidebar.markdown("---")
st.sidebar.markdown(f"*Submitted: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
# Add Clear Form Button to Sidebar
if st.sidebar.button("?? Clear Form"):
return True
return False
def validate_name(name, field_name="Name"):
"""Validate name contains only letters, spaces, and hyphens."""
if not name:
return False, f"{field_name} is required"
if not re.match(r'^[A-Za-z\\\\s-]+$', name):
return False, f"{field_name} should only contain letters, spaces, and hyphens"
if len(name) < 2:
return False, f"{field_name} should be at least 2 characters long"
return True, ""
def validate_email(email):
"""Validate email format."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return False, "Please enter a valid email address"
return True, ""
def validate_phone(phone):
"""Validate phone number format."""
pattern = r'^\\\\(?([0-9]{3})\\\\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$'
if not re.match(pattern, phone):
return False, "Please enter a valid phone number"
return True, ""
def validate_age(age):
"""Validate age is between 0 and 120."""
try:
age = int(age)
if age < 0 or age > 120:
return False, "Age must be between 0 and 120"
return True, ""
except ValueError:
return False, "Please enter a valid number"
def validate_date(input_date, earliest_date=date(1990, 1, 1)):
"""Validate date is after 1989 and not more than 5 years in future."""
max_future_date = date.today() + timedelta(days=5 * 365)
if input_date < earliest_date:
return False, f"Date must be after {earliest_date.strftime('%Y-%m-%d')}"
elif input_date > max_future_date:
return False, f"Date cannot be more than 5 years in the future"
return True, ""
def main():
st.title("? Enhanced Form Validation")
# Initialize session state
if 'form_data' not in st.session_state:
st.session_state.form_data = {
'first_name': '',
'last_name': '',
'email': '',
'phone': '',
'age': '',
'birth_date': date.today(),
'start_date': date.today(),
'end_date': date.today() + timedelta(days=1),
'submitted': False
}
# Display initial sidebar content
if not st.session_state.form_data['submitted']:
st.sidebar.title("?? Form Status")
st.sidebar.info("Please fill out the form to see the summary.")
# Main form
with st.form("enhanced_validation"):
st.header("?? Personal Information")
# Name fields in columns
col1, col2 = st.columns(2)
with col1:
first_name = st.text_input(
"First Name",
value=st.session_state.form_data['first_name']
)
if first_name:
is_valid, message = validate_name(first_name, "First name")
if not is_valid:
st.error(message)
else:
st.success("Valid first name!")
with col2:
last_name = st.text_input(
"Last Name",
value=st.session_state.form_data['last_name']
)
if last_name:
is_valid, message = validate_name(last_name, "Last name")
if not is_valid:
st.error(message)
else:
st.success("Valid last name!")
# Contact Information
st.header("?? Contact Information")
email = st.text_input(
"Email",
value=st.session_state.form_data['email']
)
if email:
is_valid, message = validate_email(email)
if not is_valid:
st.error(message)
else:
st.success("Valid email format!")
phone = st.text_input(
"Phone",
value=st.session_state.form_data['phone'],
help="Format: (123) 456-7890 or 123-456-7890"
)
if phone:
is_valid, message = validate_phone(phone)
if not is_valid:
st.error(message)
else:
st.success("Valid phone number!")
# Age and Birth Date
st.header("?? Personal Details")
col3, col4 = st.columns(2)
with col3:
age = st.number_input(
"Age",
min_value=0,
max_value=120,
value=0 if not st.session_state.form_data['age'] else int(st.session_state.form_data['age'])
)
if age:
is_valid, message = validate_age(age)
if not is_valid:
st.error(message)
else:
st.success("Valid age!")
with col4:
birth_date = st.date_input(
"Birth Date",
value=st.session_state.form_data['birth_date']
)
if birth_date:
is_valid, message = validate_date(birth_date)
if not is_valid:
st.error(message)
else:
st.success("Valid birth date!")
# Schedule Information
st.header("?? Schedule")
col5, col6 = st.columns(2)
with col5:
start_date = st.date_input(
"Start Date",
value=st.session_state.form_data['start_date']
)
with col6:
end_date = st.date_input(
"End Date",
value=st.session_state.form_data['end_date']
)
if start_date and end_date and start_date >= end_date:
st.error("Start date must be before end date")
submitted = st.form_submit_button("Submit Form")
if submitted:
# Validate all fields
validations = [
validate_name(first_name, "First name"),
validate_name(last_name, "Last name"),
validate_email(email),
validate_phone(phone),
validate_age(age),
validate_date(birth_date),
(start_date < end_date, "Start date must be before end date")
]
# Check if all validations pass
if all(v[0] for v in validations):
st.success("Form submitted successfully!")
# Update session state
st.session_state.form_data.update({
'first_name': first_name,
'last_name': last_name,
'email': email,
'phone': phone,
'age': age,
'birth_date': birth_date,
'start_date': start_date,
'end_date': end_date,
'submitted': True
})
st.rerun()
else:
# Show all validation errors
for valid, message in validations:
if not valid:
st.error(message)
# Display summary in sidebar after successful submission
if st.session_state.form_data['submitted']:
if create_sidebar_markdown(st.session_state.form_data):
# Clear form if button clicked
for key in st.session_state.form_data:
if key in ['start_date', 'end_date', 'birth_date']:
st.session_state.form_data[key] = date.today()
elif key == 'submitted':
st.session_state.form_data[key] = False
else:
st.session_state.form_data[key] = ''
st.rerun()
if __name__ == "__main__":
main()
Running the Application
To run the application, use the following command in your terminal:
streamlit run form_validation.py
Open the provided local URL in your web browser to interact with the app.
Conclusion
Implementing form validation in your Streamlit app ensures that collected data is reliable and meets the required criteria. This enhances both user experience and overall application robustness.
Just as mastering the perfect kick in kickboxing requires practice and attention to form, implementing effective form validation demands careful planning and testing. Keep iterating, and your app will soon be as strong and reliable as a seasoned martial artist.
Streamlit's simplicity in form validation stands out, especially when compared to other UI frameworks. While traditional web frameworks often require extensive setup and boilerplate code, Streamlit significantly streamlines this process.
In Streamlit, form validation integrates seamlessly into the app's flow. Built-in input widgets handle basic type checking automatically, and custom validation logic can be easily added using Python's standard libraries and control structures. This approach allows developers to focus on actual validation rules rather than grappling with complex form-handling mechanisms.
Moreover, Streamlit's reactive nature enables real-time validation feedback as users input data. This immediate response enhances user experience and reduces submission errors. The ability to display validation messages directly next to relevant input fields, as shown in our example, further simplifies the creation of user-friendly forms.
Compared to frameworks requiring separate client-side and server-side validation code or additional form-handling libraries, Streamlit's approach is refreshingly straightforward. It enables developers to implement robust form validation with just a few lines of Python code, making it an excellent choice for rapid prototyping and development of data-centric applications.
Rick and Chris finished their kickboxing class feeling invigorated and went to the coffee shop and completed some pair programming with Streamlits.
Rick: "Thanks for the guidance, Chris. With proper form in both kickboxing and coding, I feel unstoppable!"
Chris: Grins "Anytime! Remember, validation isn't just for code—it's also for making sure you don't accidentally kick your trainer!"
They both laughed, heading out with a sense of accomplishment and a plan to tackle their next coding challenge.
About the Author
Rick Hightower is a seasoned software engineer and technology enthusiast passionate about exploring cutting-edge data science and web development tools. With years of experience in the tech industry, Rick enjoys simplifying complex processes and making technology accessible to developers of all skill levels.
An advocate for continuous learning, Rick shares his knowledge through articles and tutorials, helping others navigate the ever-evolving software development landscape. His exploration of form validation in Streamlit demonstrates his commitment to empowering developers to create robust and user-friendly applications.
When not coding or writing, Rick can be found kickboxing, lifting weights, hiking or just blending his love for fitness with technology, or engaging in thought-provoking conversations about the future of tech with fellow enthusiasts.
Happy coding with Streamlit! If you have any questions or need more help, feel free to explore the official Streamlit documentation or reach out to the community.