Advanced Beginner Python Password Generator Project Tutorial

Tutorial: Python Password Generator

Dylan | Feb 01, 2021

Post Thumbnail

In this post we'll be building a simple terminal-based password generator with the ability to name and store the passwords that you generate in a spreadsheet (.csv) file.

This project is good for beginners familiar with the basics of Python. Hopefully there will even be something for intermediate level Pythonistas. We'll touch on some character encoding with ASCII, list comprehensions, the walrus operator, writing into CSV files, and f-strings.

Note: the code used in this post utilizes the Walrus Operator introduced in Python version 3.8. Earlier versions of Python will raise errors when trying to use it, but you can replace the walrus operators with the equivalent code supplied in this post to use with version 3.6 and greater.

Download the project code from github here.

Unicode

Before diving into the code, it's important to understand the fundamentals of unicode for this project. ASCII is a popular widespread encoding for 128 basic English-based characters, where each digit between 0 and 127 represents a character. Today, there are much larger character encodings (such as UTF-8) that can encode any letter or character from any language. For our password generator, ASCII will suffice. If you're interested, you can read more about the long and interesting history of ASCII on Wikipedia There's a great chart halfway down the page that shows each character and its associated decimal encoding.

I strongly encourage you to open up a terminal window or an editor and explore the encoding behind some of the basic characters. You can do this by simply running the following.


print(ord('A'))
Output: 65

print(ord('z'))
Output: 122

To reverse this process and convert an integer into its associated character, we can use the following functions.


print(chr(65))
Output: A

print(chr(122))
Output: 'z'

The __init__() Method

Now that we have a fundamental understanding of character encoding with ASCII, we're all set to explore the first method in our password generator class.


import csv
import random


class PasswordManager:

def __init__(self):
self.csv_path = 'passwords.csv'

lowercase_letters = list(range(97, 123))
uppercase_letters = list(range(65, 91))
digits = list(range(48, 58))
self.password_chars = lowercase_letters + uppercase_letters + digits

First we define the path for our CSV file to store our generated passwords but we'll return to that later. What's important now are the variables lowercase_letters, uppercase_letters, and digits. The builtin Python function range() takes two numbers and produces a 'range' class object that you can iterate (loop) over. The smallest value in the range is the first number you pass to the function and the largest value is the second number minus one. We can convert a range class object into a list with the list() function.

If you remember what we learned about ASCII character encoding, you probably have a good guess why we're creating lists of integers like this. These lists all added together in the class variable self.password_chars, will be the space that we randomly select characters from!

Generating the Passwords

Next, let's explore the two methods responsible for generating our passwords, starting with the generate_random_char() method.


def generate_random_char(self, special_chars=''):
return chr(random.choice(self.password_chars + [ord(c) for c in special_chars]))

For parameters to this method, we'll allow ourselves or users the possibility to add additional special characters to include in the randomly generated passwords. Currently, we only have lowercase letters, uppercase letters, and numbers zero to nine stored in the class variable self.password_chars.

There's quite a bit happening in this single line of code so let's break it down. First, from right to left, we'll use a list comprehension to generate a list with the encodings for the special characters passed as a parameter. This expression is equivalent to the following:


special_char_list = []
for char in special_chars:
special_char_list.append(ord(char))

The same list of integers stored in special_char_list results from the list comprehension. If you're still a little confused, don't worry! These take some time to wrap your head around. You can read my earlier post on them here.

The next step is simply to combine the new list of encoded special characters with self.password_chars defined in the __init__ method of our PasswordManager class. Before using the built-in Python library random's choice method which randomly selects a single value from a list. Then we take that single randomly selected integer value and decode it into its associated character using chr()!

Now for the generate_new_password() method.


def generate_new_password(self, length=15, special_chars='!$^&*'):
return ''.join([self.generate_random_char(special_chars) for _ in range(length)])

As method parameters (inputs), we'll give users and ourselves the option to choose the length of the password they want to generate. We'll use 15 characters as the default. Next, in addition to the lowercase, uppercase, and digits stored in the class variable self.password_chars, we can define additional special characters that we wish to include in our generated passwords.

Once again, there's a lot happening in this single line but we'll break it down from right to left. First, it uses another list comprehension to generate a list of random characters. This list comprehension is equivalent to the outcome of random_chars after the following for loop:


random_chars = []
for _ in range(length):
random_chars.append(self.generate_random_char(special_chars))

Recall, self.generate_random_char() is the method we previously defined and it takes the parameter special_chars. Remember from the very begining that the range() function creates an interable object that starts from the first integer passed and ends one integer before the second integer passed. Hold up! We only passed one integer in this example. No worries, range() is great and starts at zero if you pass a single integer. One thing that may look a little strange is the use of an underscore. Oftentimes when the value being interated doesn't matter (we never actually use it), a standard convention is to use an underscore character for it.

If you wrapped your head around that list comprehension, you're gold! Finally, the last thing left to do is to convert our list of newly generated random characters into a single string. This is exactly what the built-in join() does! Join has a slightly strange syntax at first with the string out infront of it, but how it works is rather straightforward. It will combine all of the items in the list together into a string separated by whatever is in the leading string. In our case, we don't want any characters between our generated random characters so we use an empty string.

Saving the Passwords

Now we need an easy way to store the passwords that we've generated! For this, we'll define the method save_password().


def save_password(self, name, password):
with open(self.csv_path, 'a', newline='') as file:
password_writer = csv.writer(file, delimiter=',')
password_writer.writerow([name, password])
return True

First, we set two method parameters, name and password. name is simply the human-readable name we will pair with our generated password so we know which program the password belongs to! password is the newly generated password we want to save.

If you aren't that familiar with reading and writing files using Python, the first line of this method may seem really strange. No worries, with open() as file: will simply open any file you want to work with programmatically and store it in the variable file so you can work with it. Now let's explore the three parameters inside of the built-in open() function.

First, recall the class variable self.csv that we set in the __init__ method. This is the name of the CSV file we will store our passwords in. This is the name of the file we want to open. (Note: if this file does not currently exist, Python will create it!) Next, the parameter 'a' stands for append, this is the operation we would like to perform on the file. Other options are 'r', 'w', and '+', for read, write, and read+write respectively. Finally, newline is just the character we wish to use in our file when we append something. Read more about the open() function in the Official Documentation.

Next, once our CSV file "passwords.csv" is opened by Python, we use the built-in library csv to define a csv.writer() that we'll use to "write" new passwords into our CSV file. This function requires the file object we want to write into and a delimiter. A deliter is a character that separates values. CSV stands for comma-separated values and as such, values are often separated by a comma so that's exactly what we'll use here.

Finally, csv.writer expects a single row in the CSV file to be inside of a list. Each index in the list corresponds to a column in our CSV. In the first column we'll store the name of our password and in the second column, we'll store the actual password. Then we call the method writerow() from the csv.writer object we stored in password_writer. When we leave the indentation underneath the with open() as file: line, Python will automatically safely close our file for us.

Command-Line Interface

Let's write a final main() method that will give us a nice interface in the command line to create and easily store new passwords. A command-line interface is often referred to as CLI.


def main(self):
while True:
print("Enter 'q' anytime to exit...")
if (pass_name := input('Enter the name for the new password you wish to generate: ')) == 'q':
break

new_password = self.generate_new_password()
print(f'New password for "{pass_name}" is {new_password}')

if (response := input('Would you like to save this new password? (y/n): ')) == 'y':
self.save_password(pass_name, new_password)
print(f'{pass_name} saved successfully.')
elif response == 'q':
break

Once we start our program, we want our user to be able to continue generating passwords as long as they want. We'll give our user the option to enter 'q' at any moment to exit the application.

First, we'll take an input that we intend to use as the name for our new password and using the walrus operator (:=) store it immediately in the variable pass_name. We can also immediately check if the entered value/pass_name variable is equal to 'q', in which case we use break to exit the main program's while loop. In this situation, the walrus operation is equivalent to:


pass_name := input('Enter the name for the new password you wish to generate: ')
if pass_name == 'q':
break

It just allows us to both assign and assess the pass_name variable in a single line. Be aware, this is perhaps one of the most controversial additions to Python and some have very strong opinions about it. To read more about how the operator works, read the official docs and to read about the drama and backlash surrounding the addition of this operator check out Packt's overview here.

Enough drama, let's get back to the code! Assuming that the user entered a name for the password, the next part of the code calls our self.generate_new_password() method and prints it to the terminal using an f-string. f-strings are an incredible addition to Python that lets you preface a string with an f and then using curly brackets inside of the string to insert variables, call functions, or even perform some logic. In this example, we'll only insert some variables into the string.

Next, we'll use another walrus operator to ask the user if they would like to save the password and check if they entered the character 'y'. This code is equivalent to the following:


response = input('Would you like to save this new password? (y/n): ')
if response == 'y':
self.save_password(pass_name, new_password)
print(f'{pass_name} saved successfully.')
elif response == 'q':
break

If 'y' was entered, we'll call our trusty self.save_password() method and pass in the name our user previously entered and the new password that was just generated before letting our user know it was saved successfully. We'll finish with a quick check to see if the user entered 'q' to quit and if they did, we'll again break out of the main while loop.

Final Project Code


import csv
import random


class PasswordManager:

def __init__(self):
self.csv_path = 'passwords.csv'

lowercase_letters = list(range(97, 123))
uppercase_letters = list(range(65, 91))
digits = list(range(48, 58))
self.password_chars = lowercase_letters + uppercase_letters + digits

def main(self):
while True:
print("Enter 'q' anytime to exit...")
if (pass_name := input('Enter the name for the new password you wish to generate: ')) == 'q':
break

new_password = self.generate_new_password()
print(f'New password for "{pass_name}" is {new_password}')

if (response := input('Would you like to save this new password? (y/n): ')) == 'y':
self.save_password(pass_name, new_password)
print(f'{pass_name} saved successfully.')
elif response == 'q':
break

def generate_new_password(self, length=15, special_chars='!$^&*'):
return ''.join([self.generate_random_char(special_chars) for _ in range(length)])

def generate_random_char(self, special_chars=''):
return chr(random.choice(self.password_chars + [ord(c) for c in special_chars]))

def save_password(self, name, password):
with open(self.csv_path, 'a', newline='') as file:
password_writer = csv.writer(file, delimiter=',')
password_writer.writerow([name, password])
return True


if __name__ == '__main__':
password_manager = PasswordManager()
password_manager.main()

Running the Program

Open up your command prompt or terminal and go to the directory where the main.py file for this project is stored. Once you're there, if you have Python properly added to your path, you can type the following command to start your password generator!


python main.py

Additional Resources

Recommended Improvements

I'm glad you finished this tutorial and I really hope you learned a thing or two! The best way to really cement your learning and accelerate your progress is to take what you learn in a tutorial and try to immediately apply it on your own. Using this little project as a base, I recommend trying to add the following features. You're bound to learn a ton!

Easier

  1. Allow the user to specify the length of the password and special characters to use in the CLI
  2. Add a method to read the passwords from "passwords.csv" and print them in the CLI
  3. Don't allow a user to add a password with the same name already in "passwords.csv"

Intermediate

  1. Add encryption/decryption to the passwords stored in the CSV to add an extra layer of security

Advanced

  1. Build a GUI using the tkinter, pyqt5, or even pygame library

If you implement any of these ideas or come up with your own to improve this project, please post links to your github repository in the comments below! We would love to see them. This project is available under the MIT License so you're free to do whatever you wish with it! As always, please post any questions in the comments below. Thanks for reading and happy coding from Nimble Coding!