Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions 4b267269-517f-4aeb-8d80-05145e7adfb3_DEBT_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Debt Report

## securepass/password.py
- **Complexity/Debt Observations:** The original code had a monolithic `main` function handling both input and application logic, and `password_report` was overly verbose and lacked structure. No type hints were present.
- **Refactors Performed:**
- Added type annotations to all function signatures.
- Split `main` into smaller helper functions: `_handle_password_generation`.
- Extracted `_get_composition` from `password_report` to simplify the analysis logic.
- Replaced hardcoded `if/elif` chains in `main` with a dictionary mapping for function calls.
- Added Google-style docstrings.
- **Verified:** Yes, with `test_password.py`.
198 changes: 123 additions & 75 deletions securepass/password.py
Original file line number Diff line number Diff line change
@@ -1,112 +1,105 @@
import random
import string
from typing import List

def generate_number_only(length: int) -> str:
"""Generates a password consisting only of digits.

def main():
option = ""
while option not in ("1", "2"):
option = input(
"What would you like to do:\n"
"1 Generate a secure password\n"
"2 Check the strength of my password\n> "
)
if option not in ("1", "2"):
print("Please choose 1 or 2.")

if option == "1":
choice = None
length = None
while choice not in (1, 2, 3, 4):
try:
choice = int(
input(
"Choose password type:\n"
"1 Mix of numbers, letters and symbols (Recommended)\n"
"2 Numbers only password\n"
"3 Letters only password\n"
"4 Symbols only password\n> "
)
)
if choice not in (1, 2, 3, 4):
print("Invalid choice, try again.")
except ValueError:
print("Invalid input, enter numbers only.")
while length not in range(4, 21):
try:
length = int(input("Enter your desired length (between 4 and 20): "))
if length not in range(4, 21):
print("Invalid length, try again.")
except ValueError:
print("Invalid input, enter numbers only.")

if choice == 1:
passwd = mix_of_all(length)
elif choice == 2:
passwd = generate_number_only(length)
elif choice == 3:
passwd = generate_letters_only(length)
else:
passwd = generate_symbols_only(length)

print("Here is your password:", passwd)

if input("Would you like a report for this password? (y/n): ").lower() == "y":
print(password_report(passwd))
Args:
length: The length of the password.

else: # option == "2"
existing = input("Enter the password you want to check: ")
print(password_report(existing))


def generate_number_only(length):
Returns:
A string of random digits.
"""
digits = string.digits
return "".join(random.choice(digits) for _ in range(length))


def generate_letters_only(length):
def generate_letters_only(length: int) -> str:
"""Generates a password consisting only of letters.

Args:
length: The length of the password.

Returns:
A string of random letters.
"""
letters = string.ascii_letters
return "".join(random.choice(letters) for _ in range(length))


def generate_symbols_only(length):
def generate_symbols_only(length: int) -> str:
"""Generates a password consisting only of symbols.

Args:
length: The length of the password.

Returns:
A string of random symbols.
"""
symbols = "!@#$%^&*()-_=+[]{};:,.<>?/\\|"
return "".join(random.choice(symbols) for _ in range(length))


def mix_of_all(length):
def mix_of_all(length: int) -> str:
"""Generates a password consisting of letters, digits, and symbols.

Args:
length: The length of the password.

Returns:
A string of random mixed characters.
"""
pool = string.ascii_letters + string.digits + "!@#$%^&*()-_=+[]{};:,.<>?/\\|"
return "".join(random.choice(pool) for _ in range(length))


def password_report(password: str) -> str:
recommended_length = 8
def _get_composition(password: str) -> tuple:
"""Counts character types in a password.

length = len(password)
upper = lower = digits = symbols = 0
for letter in password:
if letter.isupper():
Args:
password: The password string to analyze.

Returns:
A tuple of (upper, lower, digits, symbols) counts.
"""
upper = lower = digits = symbols = 0
for char in password:
if char.isupper():
upper += 1
elif letter.islower():
elif char.islower():
lower += 1
elif letter.isdigit():
elif char.isdigit():
digits += 1
else:
symbols += 1
return upper, lower, digits, symbols


def password_report(password: str) -> str:
"""Generates a security report for a given password.

Args:
password: The password string to analyze.

Returns:
A human-readable report string.
"""
recommended_length = 8
length = len(password)
upper, lower, digits, symbols = _get_composition(password)

parts = []

# Length report
diff = recommended_length - length
if diff > 0:
if length < recommended_length:
parts.append(
f"The password has a length of {length} characters, {diff} less than the recommended {recommended_length}."
f"The password has a length of {length} characters, {recommended_length - length} less than the recommended {recommended_length}."
)
else:
parts.append(
f"The password has a length of {length} characters, which meets or exceeds the recommended {recommended_length}."
)

# Composition report
parts.append(
f"It has {upper} uppercase letter(s), {lower} lowercase letter(s), {digits} number(s), and {symbols} symbol(s)."
)
Expand All @@ -122,14 +115,69 @@ def password_report(password: str) -> str:
suggestions.append("add a symbol for extra strength")

if suggestions:
parts.append(
"To improve this password, you could " + ", ".join(suggestions) + "."
)
parts.append("To improve this password, you could " + ", ".join(suggestions) + ".")
else:
parts.append("This password has a good mix of character types.")

return " ".join(parts)


def main() -> None:
"""Main function for interactive password generation and checking."""
option = ""
while option not in ("1", "2"):
option = input(
"What would you like to do:\n"
"1 Generate a secure password\n"
"2 Check the strength of my password\n> "
)
if option not in ("1", "2"):
print("Please choose 1 or 2.")

if option == "1":
_handle_password_generation()
else:
existing = input("Enter the password you want to check: ")
print(password_report(existing))


def _handle_password_generation() -> None:
"""Handles the password generation flow."""
choice = 0
while choice not in (1, 2, 3, 4):
try:
choice = int(
input(
"Choose password type:\n"
"1 Mix of numbers, letters and symbols (Recommended)\n"
"2 Numbers only password\n"
"3 Letters only password\n"
"4 Symbols only password\n> "
)
)
except ValueError:
print("Invalid input, enter numbers only.")

length = 0
while length not in range(4, 21):
try:
length = int(input("Enter your desired length (between 4 and 20): "))
except ValueError:
print("Invalid input, enter numbers only.")

passwd_map = {
1: mix_of_all,
2: generate_number_only,
3: generate_letters_only,
4: generate_symbols_only,
}

passwd = passwd_map[choice](length)
print("Here is your password:", passwd)

if input("Would you like a report for this password? (y/n): ").lower() == "y":
print(password_report(passwd))


if __name__ == '__main__':
main()
26 changes: 26 additions & 0 deletions securepass/test_password.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
from securepass.password import generate_number_only, generate_letters_only, password_report

class TestPassword(unittest.TestCase):
def test_generate_number_only(self):
length = 10
result = generate_number_only(length)
self.assertEqual(len(result), length)
self.assertTrue(result.isdigit())

def test_generate_letters_only(self):
length = 10
result = generate_letters_only(length)
self.assertEqual(len(result), length)
self.assertTrue(result.isalpha())

def test_password_report(self):
result = password_report("a1B!")
self.assertIn("4 characters", result)
self.assertIn("1 uppercase", result)
self.assertIn("1 lowercase", result)
self.assertIn("1 number", result)
self.assertIn("1 symbol", result)

if __name__ == '__main__':
unittest.main()
Loading