HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

How random can you get?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
randomcanyougethow

Problem

I work for a service desk and change A LOT of passwords. Most the time the end user is a complete fool that doesn't understand the requirements for the password they want to use. So I created a program that will generate random passwords based on a randomness level (1 - 3).

Here's how the levels work:

  • Reads the passwords from a file that you made, these are the most readable passwords



  • Runs through a gibberish function and returns somewhat word like passwords complete with uppercase, lowercase, integer, and special character



  • Completely random string, no pattern to it, has uppercase, lowercase, integers, and special characters.



What I would like to know is if there is anything I can do better inside of this program, along with a way to make more readable strings output from the gibberish function.

```
import optparse
import string
import random
import itertools
import os
from random import randint

init_cons = (set(string.ascii_lowercase) - set("aeiou")
- set("qxc")
| {"bl", "br", "cl", "cr", "dr", "fl", "fr", "gl", "gr", "pl", "pr", "sk", "sl", "sm", "sn", "sp", "st",
"str", "sw", "tr"}
)
final_cons = (set(string.ascii_uppercase) - set("aeiou")
- set("qxcsj")
| {"ct", "ft", "mp", "nd", "ng", "nk", "nt",
"pt", "sk", "sp", "ss", "st"}
)
vowels = "aeiou"
SYLLABLES = map(''.join, itertools.product(init_cons,
vowels,
final_cons))

opts = optparse.OptionParser()
opts.add_option("-l", "--level", metavar="RANDOMNESS LEVEL",
dest="level", help="How random do you want the passwords [1-3]")
opts.add_option("-a", "--add", metavar="PASS TO ADD",
dest="add", help="Add a password to the password file")
opts.add_option("--test-demo", action="store_true", dest="test",
help="Run the program 5 times in each level")
(options, a

Solution

CLI

Did you read the deprecation notice on the optparse module? Use argparse instead, it will help you simplify the code.

-
Convert to integers and limit available choices to 1, 2 and 3:

parser = argparse.ArgumentParser(description='TODO')
parser.add_argument("-l", "--level", metavar="RANDOMNESS LEVEL", type=int, choices=range(1,4),
                    dest="level", help="How random do you want the passwords [1-3]")
parser.add_argument("-a", "--add", metavar="PASS TO ADD",
                    dest="add", help="Add a password to the password file")
parser.add_argument("--test-demo", action="store_true", dest="test",
                    help="Run the program 5 times in each level")
args = parser.parse_args()


-
Disallow specifying two options at once, using groups:

parser = argparse.ArgumentParser(description='TODO')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-l", "--level", metavar="RANDOMNESS LEVEL", type=int, choices=range(1,4),
                   dest="level", help="How random do you want the passwords [1-3]")
group.add_argument("-a", "--add", metavar="PASS TO ADD",
                   dest="add", help="Add a password to the password file")
group.add_argument("--test-demo", action="store_true", dest="test",
                   help="Run the program 5 times in each level")
args = parser.parse_args()


or subcommands:

parser = argparse.ArgumentParser(description='TODO')
commands = parser.add_subparser(metavar='command', dest='command')
generate = commands.add_parser('generate', help='create a new password')
generate.add_argument('level', type=int, choices=[1, 2, 3],
                      help="How random do you want the passwords [1-3]")
store = commands.add_parser('store', help='Add a password to the password file')
store.add_argument('password', help='A password to store in the file')
commands.add_parser('demo', help="Run the program 5 times in each level")
args = parser.parse_args()
if args.command is None:
    parser.error('command is required')


I like the last version much as it helps separate the logic and the associated values:

import argparse

def command_line_parser():
    parser = argparse.ArgumentParser(description='TODO')
    commands = parser.add_subparser(metavar='command', dest='command')

    generate = commands.add_parser('generate', help='create a new password')
    generate.add_argument('level', type=int, choices=[1, 2, 3],
                          help="How random do you want the passwords [1-3]")

    store = commands.add_parser('store', help='Add a password to the password file')
    store.add_argument('password', help='A password to store in the file')

    commands.add_parser('demo', help="Run the program 5 times in each level")

    args = parser.parse_args()
    if args.command is None:
        parser.error('command is required')

    return args

...

if __name__ == '__main__':
    options = command_line_parser()
    if options.command == 'generate':
        print(console_main(options.level))
    elif options.command == 'store':
        add_to_file(options.password)
    elif options.command == 'demo':
        test_levels(5)


Files

Your add_to_file function append the provided word into the file. There is a special mode to the open function exactly for that purpose:

def add_to_file(word):
    with open("pass_list.txt", "a") as words:
        print(word, file=words)


You also happen to use the plain filename in this function and take it as a parameter in read_pass_from_file. I would rather define the default filename as a constant that will be used in each function:

PASSWORD_FILE = 'pass_list.txt'

def add_to_file(word):
    with open(PASSWORD_FILE, "a") as words:
        print(word, file=words)

def read_pass_from_file():
    with open(PASSWORD_FILE, "r") as password:
        data = password.readlines()
    return random.choice(data).rstrip('\n')


You may also note the use of .readlines() instead of .read().splitlines() that required twice as much as memory. The drawback being that, contrary to splitlines, readlines will keep the newline at the end of each string, so we need to clean it.

Lastly, I would use an absolute path for the password file rather than a file in the current working directory. It let you easily launch it from an other folder or even make it executable and put it in your PATH:

import os.path

PASSWORD_FILE = os.path.join(
    os.path.dirname(os.path.abspath(__file__)),
    'pass_list.txt')


This way, pass_list.txt will always be a file in the same folder than your script.

Default values

As you don't allow access to these through the command line, you may want to define default values for each of your "main" functions. You already had them for random_strings, we just modified the logic for read_pass_from_file, so there is only gibberish missing. Doing so will allow you to simplify console_main since it now receive integers direct

Code Snippets

parser = argparse.ArgumentParser(description='TODO')
parser.add_argument("-l", "--level", metavar="RANDOMNESS LEVEL", type=int, choices=range(1,4),
                    dest="level", help="How random do you want the passwords [1-3]")
parser.add_argument("-a", "--add", metavar="PASS TO ADD",
                    dest="add", help="Add a password to the password file")
parser.add_argument("--test-demo", action="store_true", dest="test",
                    help="Run the program 5 times in each level")
args = parser.parse_args()
parser = argparse.ArgumentParser(description='TODO')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-l", "--level", metavar="RANDOMNESS LEVEL", type=int, choices=range(1,4),
                   dest="level", help="How random do you want the passwords [1-3]")
group.add_argument("-a", "--add", metavar="PASS TO ADD",
                   dest="add", help="Add a password to the password file")
group.add_argument("--test-demo", action="store_true", dest="test",
                   help="Run the program 5 times in each level")
args = parser.parse_args()
parser = argparse.ArgumentParser(description='TODO')
commands = parser.add_subparser(metavar='command', dest='command')
generate = commands.add_parser('generate', help='create a new password')
generate.add_argument('level', type=int, choices=[1, 2, 3],
                      help="How random do you want the passwords [1-3]")
store = commands.add_parser('store', help='Add a password to the password file')
store.add_argument('password', help='A password to store in the file')
commands.add_parser('demo', help="Run the program 5 times in each level")
args = parser.parse_args()
if args.command is None:
    parser.error('command is required')
import argparse


def command_line_parser():
    parser = argparse.ArgumentParser(description='TODO')
    commands = parser.add_subparser(metavar='command', dest='command')

    generate = commands.add_parser('generate', help='create a new password')
    generate.add_argument('level', type=int, choices=[1, 2, 3],
                          help="How random do you want the passwords [1-3]")

    store = commands.add_parser('store', help='Add a password to the password file')
    store.add_argument('password', help='A password to store in the file')

    commands.add_parser('demo', help="Run the program 5 times in each level")

    args = parser.parse_args()
    if args.command is None:
        parser.error('command is required')

    return args


...


if __name__ == '__main__':
    options = command_line_parser()
    if options.command == 'generate':
        print(console_main(options.level))
    elif options.command == 'store':
        add_to_file(options.password)
    elif options.command == 'demo':
        test_levels(5)
def add_to_file(word):
    with open("pass_list.txt", "a") as words:
        print(word, file=words)

Context

StackExchange Code Review Q#145854, answer score: 7

Revisions (0)

No revisions yet.