Chapter 8: Input Validation

Scene: The “Never Trust the User” Crisis

Chaitanya is staring at a traceback error on his screen. His newly built “Student Registration” script has completely crashed. Aditi Ma’am walks up behind him and reads the error over his shoulder.

Python

ValueError: invalid literal for int() with base 10: 'Fifteen'

Aditi Ma’am: Ah. The classic ValueError. Let me guess: you asked for a student’s age, and they typed a word instead of a number?

Chaitanya: Yes! The prompt clearly said: Enter age (e.g., 15):. But someone typed “Fifteen”. When my code tried to do int('Fifteen'), the whole system crashed. Why can’t people just follow instructions?

Aditi Ma’am: Welcome to software development, Chaitanya. Rule #1: Never trust the user. Users will make typos. They will misunderstand instructions. Some will even try to break your program on purpose.

Chaitanya: So what do I do? I wrote a while loop with try and except to catch the error and ask them again. Look:

Python

while True:
    print('Enter your age:')
    age = input()
    try:
        age = int(age)
    except ValueError:
        print('Please use numeric digits.')
        continue
    if age < 5 or age > 18:
        print('Age must be between 5 and 18.')
        continue
    break

print(f'Your age is {age}.')

Aditi Ma’am: That is perfectly functional code. But look at how much effort it took just to ask one question! That’s 12 lines of code. What if the registration form has 20 questions? Are you going to write 240 lines of while loops and try/except blocks?

Chaitanya: That would take all week.

Aditi Ma’am: Exactly. Writing your own input validation is tedious and prone to bugs. Instead, we are going to use a third-party module called PyInputPlus. It does all of this heavy lifting for you in a single line of code.

Installing and Importing PyInputPlus

Aditi Ma’am: PyInputPlus is not part of the standard Python library, so you have to install it first. Open your terminal and run:

Bash

pip install pyinputplus

Chaitanya: Done. How do I import it?

Aditi Ma’am: Because pyinputplus is a bit long to type over and over, we use the as keyword to give it a shorter alias, usually pyip.

Python

import pyinputplus as pyip

The inputNum(), inputInt(), and inputFloat() Functions

Aditi Ma’am: Let’s rewrite your 12-line age validator using PyInputPlus. We want an integer, so we will use inputInt().

Python

import pyinputplus as pyip

response = pyip.inputInt(prompt='Enter your age: ')
print('Your age is ' + str(response))

Chaitanya: That’s it? What happens if I type “Fifteen” now?

Aditi Ma’am: Run it and find out.

Chaitanya: (Runs the code)

Enter your age: Fifteen
'Fifteen' is not an integer.
Enter your age: 

Chaitanya: Whoa! It automatically caught the error, printed a clean warning message, and re-prompted me! It didn’t crash!

Aditi Ma’am: Exactly. PyInputPlus functions act just like the built-in input() function, but they have a built-in while loop that refuses to let the program continue until the user provides valid data.

Aditi Ma’am: There are several numeric functions:

  • inputInt(): Accepts only whole numbers (returns an int).
  • inputFloat(): Accepts numbers with decimals (returns a float).
  • inputNum(): Accepts either one (returns an int or float depending on what the user types).

Setting Minimums, Maximums, and Ranges

Chaitanya: But wait, Ma’am. My original code also checked if the age was between 5 and 18. inputInt() just checks if it’s a number. If a user types 99, it will accept it.

Aditi Ma’am: PyInputPlus handles that with Keyword Arguments. You can pass min, max, greaterThan, and lessThan parameters to these functions.

Python

# Age must be between 5 and 18 (inclusive)
response = pyip.inputInt(prompt='Enter your age: ', min=5, max=18)

Chaitanya: Let me try typing 20.

Enter your age: 20
Number must be at maximum 18.
Enter your age: 

Aditi Ma’am: Notice that the error message is generated automatically. You didn’t have to write it.

Chaitanya: What is the difference between min/max and greaterThan/lessThan?

Aditi Ma’am: * min=5 means 5 is allowed.

  • greaterThan=5 means it must be 6 or higher (5 is NOT allowed).

The blank Keyword Argument

Chaitanya: What happens if the user just hits the Enter key without typing anything?

Aditi Ma’am: By default, PyInputPlus will not allow a blank response. It will say Blank values are not allowed. and ask again.

Chaitanya: But what if a question is optional? Like “Enter your middle name”?

Aditi Ma’am: Then you use the blank=True keyword argument.

Python

middle_name = pyip.inputStr(prompt='Enter middle name (optional): ', blank=True)

Aditi Ma’am: If the user hits Enter, the function will simply return an empty string '', and the program will move on.

The limit, timeout, and default Arguments

Chaitanya: Ma’am, what if a user is just holding down the Enter key, or spamming garbage text? I don’t want the program to stay in an infinite loop forever.

Aditi Ma’am: For the School System’s online quiz portal, you definitely need to put constraints on the user. You can use limit and timeout.

  1. limit: How many attempts the user gets before the program gives up.
  2. timeout: How many seconds the user has to answer before the program gives up.

Python

import pyinputplus as pyip

print('Quick! What is 8 x 7?')
try:
    # The user has 2 tries and 10 seconds to answer
    answer = pyip.inputInt(limit=2, timeout=10)
    
except pyip.RetryLimitException:
    print('You ran out of tries!')
except pyip.TimeoutException:
    print('You took too long!')

Chaitanya: Oh, so it actually does crash if they hit the limit? It throws an exception?

Aditi Ma’am: Yes. If they fail the limits, PyInputPlus throws either a RetryLimitException or a TimeoutException. You have to catch those with a try/except block, just like you see above.

Chaitanya: What if I don’t want it to crash? I just want to give them a score of 0 and move on to the next question.

Aditi Ma’am: Excellent question. Instead of letting it throw an exception, you can provide a default value. If the user hits the limit or timeout, the function will simply return the default value and continue normally.

Python

# If they fail 3 times, their answer defaults to 'N/A'
response = pyip.inputStr(prompt='Enter your favorite subject: ', limit=3, default='N/A')
print(f'Recorded subject: {response}')

Aditi Ma’am: Try running it and type nothing three times.

Chaitanya: (Runs code and hits Enter three times)

Enter your favorite subject: 
Blank values are not allowed.
Enter your favorite subject: 
Blank values are not allowed.
Enter your favorite subject: 
Blank values are not allowed.
Recorded subject: N/A

Chaitanya: That is incredibly smooth. No crashes, no massive if/else ladders. It just handled everything in one line.

Aditi Ma’am: That is why we use modules, Chaitanya. We let other programmers write the tedious validation logic so we can focus on building the actual School System.


Aditi Ma’am: We’ve covered the numeric validators and the basic limits. But what if you want the user to choose from a specific list? Like choosing a school “House” (Red, Blue, Green)?

Chaitanya: Can PyInputPlus do that too?

Aditi Ma’am: Yes. In Part 2, I will show you how to use inputChoice(), inputMenu(), and inputYesNo() to create interactive menus that physically cannot be broken by the user.

PART 2

Scene: The “Multiple Choice” Problem

Chaitanya is back at the keyboard, integrating PyInputPlus into the student registration form.

Chaitanya: Okay, Ma’am, the age validation is working perfectly. Now I need to ask the student which “House” they belong to: Red, Blue, Green, or Yellow.

Chaitanya: I could use inputStr() and write a while loop to check if their answer matches one of the four colors, right?

Aditi Ma’am: You could, but you don’t need to. If you have a specific list of acceptable answers, you should use the inputChoice() function. It takes a list of strings and refuses anything that isn’t on that list.

Python

import pyinputplus as pyip

# Provide the allowed choices as a list
house = pyip.inputChoice(['Red', 'Blue', 'Green', 'Yellow'], prompt='Enter your House: ')
print('Welcome to ' + house + ' House!')

Chaitanya: What happens if I type “Purple”?

Aditi Ma’am: Try it.

Chaitanya: (Types)

Enter your House: Purple
'Purple' is not a valid choice.
Enter your House: red
'red' is not a valid choice.

Chaitanya: It rejected “red”? But Red is an option!

Aditi Ma’am: Ah. inputChoice() is case-sensitive by default. But you can fix that easily. Just like in Regex, PyInputPlus functions accept a caseSensitive=False argument.

Chaitanya: That is incredibly strict, but very safe. But honestly, typing the whole word out is annoying. Users will complain.

Creating Menus Automatically (inputMenu)

Aditi Ma’am: If you want to make it user-friendly, don’t make them type the word. Make them pick a number. For that, we use inputMenu().

Aditi Ma’am: inputMenu() works exactly like inputChoice(), but it automatically formats your list into a numbered menu.

Python

print('Select your elective subject:')
subject = pyip.inputMenu(['Computer Science', 'Physical Education', 'Art', 'Music'], numbered=True)
print('You selected: ' + subject)

Chaitanya: Let me run that.

Select your elective subject:
1. Computer Science
2. Physical Education
3. Art
4. Music
> 2
You selected: Physical Education

Chaitanya: That is beautiful! I didn’t have to write the 1., 2., 3. myself. And I just typed 2 and it gave me the full string "Physical Education" back!

Aditi Ma’am: Exactly. It handles the mapping for you. You can also pass lettered=True if you prefer A., B., C. instead of numbers.

The inputYesNo() Function

Chaitanya: Next question on the form: “Do you need the school bus?” That’s just a Yes or No. I can use inputChoice(['Yes', 'No']).

Aditi Ma’am: You could, but Yes/No questions are so common that PyInputPlus has a dedicated function for them: inputYesNo().

Aditi Ma’am: The beauty of inputYesNo() is that it accepts almost any variation a user might type: Yes, yes, Y, y, No, no, N, n.

Python

bus_required = pyip.inputYesNo(prompt='Do you require school bus transport? (Y/N): ')
print('Bus status: ' + bus_required)

Output:

Do you require school bus transport? (Y/N): y
Bus status: yes

Chaitanya: Even though I typed y, the variable bus_required stored the full word yes?

Aditi Ma’am: Yes. It standardizes the data for your database. You don’t have to worry about cleaning it up later.

Combining PyInputPlus with Regex (allowRegexes and blockRegexes)

Aditi Ma’am: Now, let’s combine this with what you learned yesterday in Chapter 7. Sometimes the built-in functions like inputNum() are too strict, or not strict enough.

Chaitanya: Give me an example.

Aditi Ma’am: Suppose you want a student to enter their class standard. They should enter a number from 1 to 12. So you use inputNum(). But what if a student types Roman numerals? X for 10, or IV for 4?

Chaitanya: inputNum() will reject it. “X” is a letter, not a number.

Aditi Ma’am: Correct. But we can override that behavior using the allowRegexes keyword argument. It takes a list of Regex strings that PyInputPlus will accept, even if they normally violate the function’s rules.

Python

# Allow Roman numerals I, V, X, L, C, D, M
standard = pyip.inputNum(prompt='Enter class standard: ', allowRegexes=[r'(I|V|X|L|C|D|M)+', r'zero'])

Chaitanya: Wait, I can type “zero” too?

Aditi Ma’am: Yes, because you put r'zero' in the allowRegexes list. The function inputNum says “I only accept numbers… UNLESS it matches one of these regex patterns.”

Chaitanya: What about the opposite? What if inputNum accepts something, but I want to block it? Like preventing students from entering even numbers?

Aditi Ma’am: Then you use blockRegexes. It takes a list of Regex patterns that will be immediately rejected, even if they are otherwise valid.

Python

# Block even numbers (any number ending in 0, 2, 4, 6, 8)
odd_only = pyip.inputNum(prompt='Enter an odd number: ', blockRegexes=[r'[02468]$'])

Output:

Enter an odd number: 42
This response is invalid.
Enter an odd number: 43

Chaitanya: r'[02468]$'… Ah! The $ means “ends with”. So if the string ends with 0, 2, 4, 6, or 8, it gets blocked!

Aditi Ma’am: Exactly.

Chaitanya: What if I put the same thing in both allowRegexes and blockRegexes? Which one wins?

Aditi Ma’am: The allow list overrides the block list. If you block all words that have the letter ‘e’, but specifically allow ‘caterpillar’, then ‘caterpillar’ will be accepted, but ‘apple’ will be rejected.

Custom Validation (inputCustom)

Aditi Ma’am: Finally, Chaitanya, there will be times when Regex is simply not enough.

Chaitanya: Like when?

Aditi Ma’am: Like when you need to do math on the input to verify it. Suppose the School ID cards have a special security feature: The ID is a series of digits, but the digits must add up exactly to 10.

  • Valid: 1234 (1+2+3+4 = 10)
  • Valid: 55 (5+5 = 10)
  • Invalid: 123 (1+2+3 = 6)

Chaitanya: Regex can’t do addition.

Aditi Ma’am: Exactly. For complex logic, you write your own validation function and pass it to inputCustom().

Step 1: Write the Function The rule is simple: If the input is bad, use the raise keyword to throw an Exception (with an error message). If the input is good, return the value.

Python

def addsUpToTen(numbers):
    numbersList = list(numbers) # Convert string to list of characters
    
    # Check if they are all digits first
    for digit in numbersList:
        if not digit.isdecimal():
            raise Exception(f'"{digit}" is not a number.')
            
    # Calculate the sum
    total = 0
    for digit in numbersList:
        total += int(digit)
        
    # Check the sum
    if total != 10:
        raise Exception(f'The digits must add up to 10, not {total}.')
        
    return int(numbers) # If it survives all checks, it's valid!

Step 2: Pass it to inputCustom Aditi Ma’am: Now, instead of calling the function yourself with parentheses (), you just hand the name of the function to PyInputPlus.

Python

response = pyip.inputCustom(addsUpToTen, prompt='Enter ID digits that add up to 10: ')
print(f'Valid ID accepted: {response}')

Output:

Enter ID digits that add up to 10: 123
The digits must add up to 10, not 6.
Enter ID digits that add up to 10: 55X
"X" is not a number.
Enter ID digits that add up to 10: 145
Valid ID accepted: 145

Chaitanya: That is incredible. I built my own custom PyInputPlus function!

Aditi Ma’am: Yes. You are no longer just using tools, Chaitanya. You are building them.


Aditi Ma’am: That covers all the mechanics of PyInputPlus. In Part 3, we will put it all together to build two full projects from the book:

PART 3

Scene: Bringing It All Together

Aditi Ma’am: Chaitanya, learning tools like allowRegexes and timeout is useless unless you can orchestrate them into a real program. Today, we build two projects.

Chaitanya: I have my terminal open. What is the first one?

Aditi Ma’am: The first is a classic programmer’s joke, but we will adapt it to the School System. We call it: “How to Keep an Idiot Busy for Hours.” Or, in our case, “The Endless School Announcements.”

Chaitanya: Oh, I know this. You ask a user a Yes/No question. If they say “Yes”, you ask it again. If they say “No”, the program ends.

Aditi Ma’am: Exactly. Write it.

Project 1: The Endless Announcements

Chaitanya: Let’s see. I need a while loop, and I need pyip.inputYesNo().

Python

import pyinputplus as pyip

while True:
    prompt = 'Would you like to hear another boring school announcement?\n'
    response = pyip.inputYesNo(prompt)
    
    if response == 'no':
        break
        
print('Thank goodness. Have a nice day!')

Aditi Ma’am: Run it.

Output:

Would you like to hear another boring school announcement?
yes
Would you like to hear another boring school announcement?
y
Would you like to hear another boring school announcement?
sure
'sure' is not a valid y/n response.
Would you like to hear another boring school announcement?
no
Thank goodness. Have a nice day!

Chaitanya: That took exactly 8 lines of code. If I had written that with standard input(), I would have had to account for ‘Y’, ‘y’, ‘yes’, ‘YES’, and handle all the invalid typing. inputYesNo did all the heavy lifting.

Aditi Ma’am: Precisely. Now, let’s move to something much harder. The Principal wants an automated Multiplication Quiz for the 3rd-grade computer lab.

Project 2: The Automated Math Quiz

Aditi Ma’am: Here are the requirements for the quiz program:

  1. It must ask 10 random multiplication questions (from 0 x 0 to 9 x 9).
  2. The student only has 8 seconds to answer each question.
  3. The student only gets 3 tries per question.
  4. If they type a letter instead of a number, it counts as a try and tells them “Incorrect!”.
  5. At the end, it must print their final score.

Chaitanya: Okay, I need the random module for the numbers, and the time module to pause between questions.

Aditi Ma’am: Yes. And you will use pyip.inputStr() to collect the answer.

Chaitanya: Wait, inputStr? Why not inputNum? We are asking for a number!

Aditi Ma’am: This is a brilliant trick in PyInputPlus. We don’t just want any number. We want the correct number. We can use allowRegexes to set the exact correct answer, and blockRegexes to instantly reject everything else!

Aditi Ma’am: Here is the setup.

Python

import pyinputplus as pyip
import random, time

numberOfQuestions = 10
correctAnswers = 0

for questionNumber in range(1, numberOfQuestions + 1):
    # Pick two random numbers
    num1 = random.randint(0, 9)
    num2 = random.randint(0, 9)
    
    prompt = '#%s: %s x %s = ' % (questionNumber, num1, num2)

Chaitanya: Okay, now I need to prompt the user. But how do I dynamically set the regex to match num1 * num2?

Aditi Ma’am: You use string formatting. If num1 is 3 and num2 is 4, the correct answer is 12. So your allowed regex should literally just be ^12$.

Python

    try:
        # Right answers are allowed
        # Wrong answers are blocked with a custom message
        pyip.inputStr(prompt, allowRegexes=['^%s$' % (num1 * num2)],
                              blockRegexes=[('.*', 'Incorrect!')],
                              timeout=8, limit=3)

Chaitanya: Whoa! Look at blockRegexes=[('.*', 'Incorrect!')].

  • .* matches everything. So it blocks every single thing the user types.
  • Except, allowRegexes overrides blockRegexes! So the only thing that gets through is the correct answer.
  • And if it gets blocked, it automatically prints “Incorrect!” and asks again!

Aditi Ma’am: Exactly. Now, finish the loop. What happens if they run out of time or tries?

Chaitanya: They get an exception. I need a try/except block.

Python

    except pyip.TimeoutException:
        print('Out of time!')
    except pyip.RetryLimitException:
        print('Out of tries!')
    else:
        # This block runs ONLY if no exceptions were raised
        print('Correct!')
        correctAnswers += 1
        
    time.sleep(1) # Brief pause before the next question

Aditi Ma’am: Very good. Notice the else block at the end of the try/except chain. Many programmers forget about this. Code inside the else block only executes if the try block succeeds without any errors. If they answered correctly, no exception is thrown, the else block runs, prints “Correct!”, and adds 1 to their score.

Chaitanya: And finally, print the score outside the loop.

Python

print('Score: %s / %s' % (correctAnswers, numberOfQuestions))

Running the Quiz

Aditi Ma’am: Let’s test it.

Output:

#1: 4 x 2 = 8
Correct!
#2: 9 x 9 = 80
Incorrect!
#2: 9 x 9 = cat
Incorrect!
#2: 9 x 9 = 81
Correct!
#3: 5 x 5 = 
... (waits 8 seconds) ...
Out of time!
...
Score: 2 / 10

Chaitanya: This is amazing. I didn’t write a single if statement to check if answer == num1 * num2. PyInputPlus handled the evaluation, the retry logic, the custom error messages, and the timers.

Aditi Ma’am: That is the difference between a beginner and a professional. A beginner writes 50 lines of complex, buggy logic. A professional finds the right library, reads the documentation, and writes 15 lines of bulletproof code.

Summary Box (Chapter 8)

  • Rule #1: Never trust user input. Always validate.
  • pyinputplus: A third-party module for bulletproof input prompts.
  • Basic Functions: inputInt(), inputFloat(), inputYesNo(), inputChoice(), inputMenu().
  • Constraints: min, max, greaterThan, lessThan.
  • Limits: timeout (seconds) and limit (number of tries).
  • Handling Failures: Catch TimeoutException and RetryLimitException, or use the default keyword to avoid crashes.
  • Regex Magic: Use allowRegexes and blockRegexes to create highly specific, automated validation rules.

Aditi Ma’am: You now have complete control over the data flowing into your programs. Tomorrow, we start Chapter 9: Reading and Writing Files. You will finally learn how to save this data permanently to the hard drive so it doesn’t disappear when you close the terminal.

Chaitanya: I can’t wait. My computer lab attendance script is finally going to be useful!


Leave a Comment

💬 Join Telegram