Validate a password with helper functions

Build a modular password validator. Implement the 4 character helpers below with their own loops, then let validate_password combine their Boolean results. This is a standalone decomposition exercise; all rules and interfaces are defined here.

The password is valid when it contains an uppercase letter, a lowercase letter, a digit, a character from SPECIAL_CHARACTERS, and at least eight characters.

The 4 character helpers return True when their named character type is present and False otherwise. The supplied has_minimum_length(password, minimum=8) returns whether the length is at least minimum; use a nonnegative minimum. validate_password returns "Valid" when all rules are met and "Invalid" otherwise. The supplied length helper is already implemented; write a loop in each character helper.

Complete the supplied functions in Listing 1. Keep their signatures and use the checks below them.

Listing 1: Starter functions and supplied checks for password validation with helper functions.
SPECIAL_CHARACTERS = "!@#$%^&*()_+[]{}|;:'\",.<>?"


def has_uppercase(password: str) -> bool:
    raise NotImplementedError


def has_lowercase(password: str) -> bool:
    raise NotImplementedError


def has_digit(password: str) -> bool:
    raise NotImplementedError


def has_special_character(password: str) -> bool:
    raise NotImplementedError


def has_minimum_length(password: str, minimum: int = 8) -> bool:
    return len(password) >= minimum


def validate_password(password: str) -> str:
    raise NotImplementedError


assert validate_password("Password123!") == "Valid"
assert validate_password("password123!") == "Invalid"
assert validate_password("PASSWORD123!") == "Invalid"
assert validate_password("Password") == "Invalid"

Test each helper separately before testing the combined validator.

len(text) returns the number of characters, so len("cat") is 3 and len("") is 0. A for loop over a string visits its characters in order. String indexing starts at 0, so "cat"[0] is "c"; the index must refer to an existing character.

assert condition stops the check with an AssertionError when the condition is false. "A".isupper(), "a".islower(), and "7".isdigit() are True. These methods return Boolean character classifications; "!".isdigit() is False. character in SPECIAL_CHARACTERS checks membership in the supplied string. has_minimum_length("abc", 3) returns True; has_minimum_length("abc") returns False.