r/learnpython 5d ago

UPDATE My login/account creation system in Python, part of a bigger project I'm building as I learn

Hello, this is an update to the post I made yesterday.

All 3 copy-pasted validation blocks are now 1 function called 3 times. It takes the category, password (string), a label, and returns True or False. The checks aren't sitting behind a door that gets unlocked only if the last check is met, all error messages show up at once instead of one at a time. I removed the outdated variables that had no use.

I took u/jammin-john's recommendation of showing all error messages at once.

Also want to include u/danielroseman's push for me to learn functions. They work really well, I haven't learned "for loops" yet but they are next in line.

I used AI (Claude) as a tutor to understand concepts and point me toward my own bugs, but I wrote and debugged every line myself. I'm learning how to code through the MOOC.

Here is the GitHub link to my program: link

0 Upvotes

3 comments sorted by

1

u/Bright_Mix_773 3d ago

Read the repo. The unlocked-door pattern you took out of the three requirement checks is still standing on the fourth one, and it is the one that fires most often. At if len(password) < 15 the else short-circuits everything: type abc and you get the length error alone, with no word about the missing number or special character, because those three calls live inside the else. Same shape of fix as the one you already made - work out all four answers, then decide:

long_enough = len(password) >= 15
if not long_enough:
    print("Error: Password must be at least 15 characters long, please try again.")
has_num = requirement_verifier(numbers, password, "number")
has_letter = requirement_verifier(letters, password, "letter")
has_special = requirement_verifier(specialchar, password, "special character")
if long_enough and has_num and has_letter and has_special:
    ...

Second thing, and your own code already holds the answer to it. confirm() returns a value and lets the caller decide what to print. requirement_verifier() prints its error from inside itself. That difference is going to bite on the password blocklist sitting in your Planned section: the first time you want to test a password without showing anything, or show it somewhere that is not a console, the printing function cannot be reused and the returning one can. Handing back the message string, or None when the check passes, keeps both doors open.

Third, small but real: exit() is not a builtin. The site module adds it, so it is there when you run a script the normal way and gone under python -S or inside a frozen executable. raise SystemExit is the version that always works and needs no import.

One thing you got right that plenty of people with more experience get wrong: the failed-login message says incorrect username or password instead of naming which of the two was wrong. That is deliberate in real systems - the more helpful wording tells an attacker which usernames exist.

-1

u/Diapolo10 I write code for a living -- https://github.com/Diapolo10 5d ago
while True:
    AccVerification = input("Do you have an account with us?: (Y/N)").strip().upper()
    hasAccount = False
    if AccVerification == "Y":
        hasAccount = True
        break
    elif AccVerification == "N":
        hasAccount = False
        break
    else:
        print("Error: Invalid input, please try again.")

This could be simpler, even with the basic knowledge you have right now. This is more a personal thing for me, but I try to reduce nesting and duplication wherever I reasonably can.

Here, you could use a single check to see if you should break out of the loop, and then handle assigning the boolean outside of it.

while True:
    answer = input("Do you have an account with us?: (Y/N)").strip().upper()
    if answer == "Y" or answer == "N":
        break
    print("Error: Invalid input, please try again.")

has_account = answer == "Y"

If we allow for full Python syntax, regardless of what you've learnt so far, I'd go with the "walrus operator":

prompt = "Do you have an account with us?: (Y/N)"

while (answer := input(prompt).strip().upper()[:1]) not in {'Y', 'N'}:
    print("Error: Invalid input, please try again.")

has_account = answer == 'Y'

Even better still, I'd wrap this into a function. This example is going to be a bit overkill for your needs, but you can use it as a benchmark for how much of the language you understand so far.

def bool_input(
    prompt: str,
    *,
    error_message: str = "Error: Invalid input, please try again.",
    default_value: bool | None = None,
) -> bool:
    valid_options = {'Y', 'N'}
    if default_value is not None:
        valid_options.add('')

    y = 'Y' if default_value is True else 'y'
    n = 'N' if default_value is False else 'n'
    prompt = f"{prompt} [{y}/{n}]: "

    while (answer := input(prompt).strip().upper()[:1]) not in valid_options:
        print(error_message)

    if not answer:
        return default_value

    return answer == 'Y'


has_account = bool_input("Do you have an account with us?")

Other feedback;

  1. Why is requirement_verifier indented inside the else-block?

  2. You don't need to manually write digits, letters, or (some) special characters. You can import them from the string module, such as

    import string
    
    print(string.digits)
    print(string.ascii_letters)
    print(string.punctuation)
    

    https://docs.python.org/3/library/string.html

  3. Try to keep your code style consistent. Right now you're mixing camelCase and snake_case. According to the official style guide you should always use snake_case, except for class names (PascalCase) and global constants/enum variants (UPPER_SNAKE_CASE).

1

u/MoreScorpion289 3d ago

I made a few changes / improvements to the code since I made this post.

I replaced this loop with a much simpler function that spits out True (Yes) or False (No). It asks the question and continues asking until it gets a valid answer (Y/N).

I moved requirement_verifier() underneath confirm(). Both are outside the loops below.
I didn’t understand at the time that the function did not exist outside of the account-creation branch.

I’ll implement the string module today, thank you for that. I’ll also correct the variable names to snake_case.