Skip to content

Instantly share code, notes, and snippets.

@rogfrich
Created September 14, 2020 15:47
Show Gist options
  • Save rogfrich/20ae98c02cae974b610732074f63dad3 to your computer and use it in GitHub Desktop.
Save rogfrich/20ae98c02cae974b610732074f63dad3 to your computer and use it in GitHub Desktop.
My solution to exercise 37 in "Exercises for Programmers: 57 Challenges to Develop Your Coding Skills" by Brian P Hogan.
"""
Exercises for Programmers: 57 Challenges to Develop Your Coding Skills.
Exercise 37: Password Generator.
Requirements:
- Create a program to generate a secure password.
- Prompt for length, number of special characters, and the number of numeric chars.
Constraints:
- Store characters used to generate passwords in lists.
- Add some randomness to the password generation.
Extra credit challenges:
- Randomly convert vowels to numbers such as 3 for E and 4 for A.
- Have the program present a few options for the user to choose from.
- Place the password on the user's clipboard when generated.
"""
import pyperclip
import secrets
import random # used in one specific place because secrets does not have an equivalent to random.shuffle()
import string
class PasswordGenerator:
def __init__(self, password_length=None, qty_special_characters=None, qty_numeric_characters=None):
# get user inputs, or receive them via the function call for testing purposes.
if not password_length:
self.password_length = self.get_length_from_user()
else:
self.password_length = password_length
if not qty_special_characters:
self.qty_special_chars = self.get_qty_special_chars_from_user()
else:
self.qty_special_chars = qty_special_characters
if not qty_numeric_characters:
self.qty_numeric_chars = self.get_qty_numeric_chars_from_user()
else:
self.qty_numeric_chars = qty_numeric_characters
self.generated_passwords = []
self.vowel_replacements = {
"a": "4",
"e": "3",
"i": "1",
"o": "0",
"u": "9"
}
def get_length_from_user(self):
while True:
length = input("Enter the length of the password you want to create: > ")
if length.isnumeric():
return int(length)
def get_qty_special_chars_from_user(self):
while True:
qty_special_characters = input("Enter the quantity of SPECIAL characters to include: > ")
if qty_special_characters.isnumeric() and int(qty_special_characters) < self.password_length:
return int(qty_special_characters)
def get_qty_numeric_chars_from_user(self):
while True:
qty_numeric_chars = input("Enter the quantity of NUMERIC characters to include: > ")
if qty_numeric_chars.isnumeric() and int(qty_numeric_chars) + self.qty_special_chars < self.password_length:
return int(qty_numeric_chars)
def generate_password(self):
pw = []
# Randomly select numeric characters
for i in range(int(self.qty_numeric_chars)):
pw.append(str(secrets.randbelow(10)))
# Randomly select special characters
for i in range(int(self.qty_special_chars)):
pw.append(secrets.choice("!@#$%^&*()"))
# Randomly select alpha characters. 50% chance that a vowel will be converted to a number.
for i in range(int(self.password_length) - (int(self.qty_special_chars) + int(self.qty_numeric_chars))):
random_char = secrets.choice(string.ascii_letters)
if random_char.lower() in "aeiou" and secrets.choice([0, 1]) == 0:
pw.append(self.vowel_replacements[random_char.lower()])
continue
pw.append(random_char)
assert len(pw) == self.password_length
random.shuffle(pw)
return ''.join(pw)
def main():
QTY_OF_OPTIONS = 3 # The number of generated passwords presented to the user to choose from
pw = PasswordGenerator()
passwords = {}
for index, _ in enumerate(range(QTY_OF_OPTIONS)):
passwords[str(index)] = pw.generate_password()
print("\nPlease choose a password from the randomly generated list below:")
for k, v in passwords.items():
print(f"{k}: {v})")
while True:
choice = input("> ")
if choice in passwords.keys():
break
chosen_password = passwords[choice]
pyperclip.copy(chosen_password)
print(f"\"{chosen_password}\" copied to clipboard")
def tests():
# Run "pytest main.py" from the terminal command line
pg = PasswordGenerator(
password_length=8,
qty_numeric_characters="1",
qty_special_characters="1",
)
pw = pg.generate_password()
print(pg.qty_numeric_chars)
# Test that length of generated password is correct:
assert len(pw) == 8
# Test that there is only one special char in generated password:
number_of_special_chars = len([x for x in pw if x in "!@#$%^&*()"])
assert number_of_special_chars == 1
# Test that there are 1 < n <len(pw) numeric chars in pw (exact number is indeterminate because of random)
number_of_numeric_chars = len([x for x in pw if x.isnumeric()])
assert 1 <= number_of_numeric_chars < len(pw)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment