Skip to Content
Course content

154: Practice Exercise: Building a Robust Input Validation Layer

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you know how to get input from a user, but in a real-world production environment, the golden rule is: never trust the user. If you expect an integer and they give you a string, or if you expect a positive number and they give you -500, your program shouldn't just crash with a traceback. It should handle it gracefully.

I want to show you how to build a validation layer for a user profile system. Instead of sprinkling if/else blocks all over your main logic, we're going to isolate the validation so the rest of your app can assume the data it receives is already clean.

Drafting the Naive Validation Logic

Let's start with a simple requirement: we need a username (3-15 characters, alphanumeric) and an age (18-100). My first instinct is usually to just write a function that returns a boolean. It's quick, but it's a bit limited because it doesn't tell the user why the input failed.

def validate_user_data(username, age):
    if len(username) < 3 or len(username) > 15:
        return False
    if not username.isalnum():
        return False
    if age < 18 or age > 100:
        return False
    return True

This works, but it's "silent." If this returns False, the user just knows something is wrong, but they don't know if it was their username or their age. That's a terrible user experience.

Oops—I Forgot About Type Casting

Here is where I usually trip up when I'm rushing a prototype. I'll start calling my function like this:

# Imagine this is coming from an input() call
user_name = "DevDan"
user_age = "25" 

if validate_user_data(user_name, user_age):
    print("Success!")
else:
    print("Invalid input.")

If you run this, Python is going to throw a TypeError. Why? Because user_age is a string, and I'm trying to compare it using < and > against integers in my function. I forgot that input() always returns a string. In a real app, this crash is exactly what we're trying to avoid.

To fix this, I need to handle the type conversion inside the validation layer, wrapped in a try/except block, so the app doesn't blow up if the user types "twenty-five" instead of "25".

Creating a Dedicated Validation Engine

To make this truly robust, I'm going to move away from booleans and start using custom exceptions. This allows us to pass a specific error message back up the chain. I'll create a specialized ValidationError class. This is a common pattern in professional Python frameworks like Django or Pydantic.

class ValidationError(Exception):
    """Custom exception for input validation errors."""
    pass

def validate_profile(data):
    # Validate Username
    username = data.get("username", "")
    if not (3 <= len(username) <= 15):
        raise ValidationError("Username must be between 3 and 15 characters.")
    if not username.isalnum():
        raise ValidationError("Username must be alphanumeric.")

    # Validate Age
    try:
        age = int(data.get("age", 0))
    except ValueError:
        raise ValidationError("Age must be a valid number.")

    if not (18 <= age <= 100):
        raise ValidationError("Age must be between 18 and 100.")
    
    return True

Now, the logic is separated. The validate_profile function doesn't care where the data comes from—it just cares that the data follows the rules. If it fails, it tells us exactly why.

Integrating the Layer into the Main Loop

The final piece is the "retry loop." We don't want the program to end just because of one typo. We'll wrap the validation call in a while loop that only breaks once the ValidationError is no longer being raised.

while True:
    user_input = {
        "username": input("Enter username: "),
        "age": input("Enter age: ")
    }
    
    try:
        validate_profile(user_input)
        print("Profile validated successfully!")
        break 
    except ValidationError as e:
        print(f"Input Error: {e}")
        print("Please try again.\n")

By isolating the validation into its own layer, we've made the code modular. If we later decide that usernames can include underscores, we only have to change one line in the validate_profile function, and the rest of our application remains untouched.




📋 Practical Task

Exercise: Building a Secure Password and Email Validator

Your task is to expand the validation layer we built. Create a program that asks a user for an email address and a password, and validates them using a custom ValidationError class.

Your validation layer must enforce the following rules:

  • Email: Must contain at least one "@" symbol and at least one "." (dot).
  • Password: Must be at least 8 characters long and contain at least one digit.

Requirements:

  1. Define a custom ValidationError exception.
  2. Create a function validate_credentials(credentials) that takes a dictionary and raises the custom exception with a specific message for each rule violation.
  3. Implement a while loop that repeatedly prompts the user for their email and password until both are valid.
  4. Ensure that the program does not crash if the user provides empty inputs.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.