DEV Community

Nivethan
Nivethan

Posted on

A Mail Command in Python - 02 Adding Flags

This section is pretty long as I explain everything but you can jump to the bottom to see the full arguments we have set up. The great thing is that there is probably something very similar to argparse in a variety of languages, both rust and node have ports of the python library :)

Now that we have the core of our little program, we can now add the various flags we mentioned at the beginning of the previous chapter. We'll be adding optional flags, required flags, flags that can be duplicated, and finally a positional argument. We have quite a bit to get through!

The first flag we'll add is the flag that started this entire adventure. The HTML flag!

#!/usr/bin/env python3

import argparse

def main():
    parser = argparse.ArgumentParser(description="Mail replacement in python")

    parser.add_argument("-html","--html-flag", help="Set e-mail content type to html", action="store_true")

    args = parser.parse_args()
    print(args)

main()
Enter fullscreen mode Exit fullscreen mode

We add an argument to the parser flag and we can specify a few different things. We can specify the short form of our command and we can specify the long form. The convention is that a short form uses a single dash whereas the long form of a command uses two dashes.

Side note, I use short forms on the command line but for scripts, I use long forms. This way it's obvious what flags the script is using.

Once we defined the flags, we set the help text. This will display when the user runs the help for our application.

The action is a big of magic. The store_true string is telling argparse that this flag is a boolean, it won't be followed by any text. This means that the existence of the html flag in the command means the flag is True. If the flag isn't in the command then the html_flag variable will be False.

Now you might be wondering why I said html_flag. Good question. More magic! It looks like argparse uses the flag names to infer the destination variable. This means --html-flag will become html_flag. We can also add a dest manually as well.

    parser.add_argument("-html","--html-flag", help="Set e-mail content type to html", action="store_true", dest="htmlFlag")
Enter fullscreen mode Exit fullscreen mode

This would but the value of this flag in the variable htmlFlag.

The inferred names are all pretty good so we'll be using them going forward.

Now let's run our application with our new html flag.

> ./pymail -html
Namespace(html_flag=True)
Enter fullscreen mode Exit fullscreen mode

We can see that args contains a object with our flag and it's value.

We can also run out command without the html flag:

> ./pymail
Namespace(html_flag=False)
Enter fullscreen mode Exit fullscreen mode

Perfect! This exactly how we want our flag to work, the presence of the flag should give us a true and the absence of it should be false.

Now let's add the subject line as a flag.

    parser.add_argument("-html","--html-flag", help="Set e-mail content type to html", action="store_true")
    parser.add_argument("-s","--subject", help="Specify subject on command line", default="")
Enter fullscreen mode Exit fullscreen mode

Once again we define a short form and long form of our flag. If we had just the short form, we would set the dest keyword to assign the flag to a variable.

Here we introduce a new keyword, default. This let's us, as the name says, set the default for this flag. We could remove the default but then we will need to manually check the subject variable to see if it has anything in it.

> ./pymail
Namespace(html_flag=False, subject='')
Enter fullscreen mode Exit fullscreen mode
> ./pymail -s "A subject line"
Namespace(html_flag=False, subject='A subject line')
Enter fullscreen mode Exit fullscreen mode

So far our flags have been optional, the next flag we'll add is the from address and we want this to be required.

    parser.add_argument("-s","--subject", help="Specify subject on command line", default="")
    parser.add_argument("-r", "--from-address", help="Sets  the  From  address.", required=True)
Enter fullscreen mode Exit fullscreen mode

This is also pretty straightforward, the only new thing is we have a required keyword that we can set to true.

> ./pymail
usage: pymail [-h] [-html] [-s SUBJECT] -r FROM_ADDRESS
pymail: error: the following arguments are required: -r/--from-address
Enter fullscreen mode Exit fullscreen mode

Now our command line utility will throw an error if the from address is missing.

This is great!

> ./pymail s "A body" -r nivethant@example.com
Namespace(from_address='nivethant@example.com', html_flag=False, subject='A body')
Enter fullscreen mode Exit fullscreen mode

The next flag we'll add is the flag to add cc addresses. Here we want to be able to specify multiple cc addresses. We can do this two ways, we can set up our flag so it takes multiple arguments but a single -c or we can have multiple -c flags for each cc address we want.

The second option is what I'll be using as that is what the mail command does.

    parser.add_argument("-r", "--from-address", help="Sets  the  From  address.", required=True)
    parser.add_argument("-c", "--cc-address", help="Send carbon copies to user.", action="append", default=[])
Enter fullscreen mode Exit fullscreen mode

The cc flag has it's action as append. This means that all of the cc flags will be gathered together into a single list. We also set the default to be an empty list as this way, like the subject, we won't have to worry about making sure the variable is usable.

Now we are starting to get a program that can handle quite a few different things!

> ./pymail -s "A body" -c test@example.com -c another@example.com -r nivethant@example.com
Namespace(cc_address=['test@example.com', 'another@example.com'], from_address='nivethant@example.com', html_flag=False, subject='A body')

Enter fullscreen mode Exit fullscreen mode

The next flag to add is the attachments. This will be the same as the cc.

    parser.add_argument("-c", "--cc-address", help="Send carbon copies to user.", action="append", default=[])
    parser.add_argument("-a", "--attachment", help="Attach the given file to the message.", action="append", default=[])
Enter fullscreen mode Exit fullscreen mode

With that, we are now at the final thing we need our program to read in from the command line. This is the to address. The to address will be a positional argument and positional arguments are required.

    parser.add_argument("-a", "--attachment", help="Attach the given file to the message.", action="append", default=[])
    parser.add_argument("to_address", help="Specify the to address")
Enter fullscreen mode Exit fullscreen mode

This has a big of magic as the dashes is what signifies if an argument is a flag or if it is a positional argument. The to_address here is a positional argument.

Now if we try to run an earlier command:

> ./pymail -s "A body" -c test@example.com -c another@example.com -r nivethant@example.com
usage: pymail [-h] [-html] [-s SUBJECT] -r FROM_ADDRESS [-c CC_ADDRESS]
              [-a ATTACHMENT]
            to_address
pymail: error: the following arguments are required: to_address
Enter fullscreen mode Exit fullscreen mode

We need to give a destination e-mail address.

> ./pymail -s "A body" -c test@example.com -c another@example.com -r nivethant@example.com nivethan@test.com
Namespace(attachment=[], cc_address=['test@example.com', 'another@example.com'], from_address='nivethant@example.com', html_flag=False, subject='A body', to_address='nivethan@test.com')
Enter fullscreen mode Exit fullscreen mode

Almost done! I also would like to send e-mails to multiple people at the same time. Let's set up the to as both a positional argument and also a flag.

This is a simple change as we just need to specify a flag for the to and set the action to append for both the flag "to" and the positional argument "to".

    parser.add_argument("-a", "--attachment", help="Attach the given file to the message.", action="append", default=[])
    parser.add_argument("-t", "--to-address", help="Specify multiple to addresses", action="append")
    parser.add_argument("to_address", help="Specify the to address", action="append")
Enter fullscreen mode Exit fullscreen mode

Now we can have multiple to addresses and also specify a to with a flag.

> ./pymail -s "A body" -c test@example.com -c another@example.com -r nivethant@example.com -t another_to@example.com niv
ethan@test.com
Namespace(attachment=[], cc_address=['test@example.com', 'another@example.com'], from_address='nivethant@example.com', html_flag=False, subject='A body', to_address=['another_to@example.com', 'nivethan@test.com'])
Enter fullscreen mode Exit fullscreen mode

Voila! With that we are done! We have a number of flags that we can now use when we write the mail portion of our command line application.

This is the full code below that we have so far:

#!/usr/bin/env python3

import argparse

def main():
    parser = argparse.ArgumentParser(description="Mail replacement in python")

    parser.add_argument("-html","--html-flag", help="Set e-mail content type to html", action="store_true")
    parser.add_argument("-s","--subject", help="Specify subject on command line", default="")
    parser.add_argument("-r", "--from-address", help="Sets  the  From  address.", required=True)
    parser.add_argument("-c", "--cc-address", help="Send carbon copies to user.", action="append", default=[])
    parser.add_argument("-a", "--attachment", help="Attach the given file to the message.", action="append", default=[])
    parser.add_argument("-t", "--to-address", help="Specify multiple to addresses", action="append")
    parser.add_argument("to_address", help="Specify the to address", action="append")

    args = parser.parse_args()
    print(args)

main()
Enter fullscreen mode Exit fullscreen mode

It's not much but it is pretty powerful.

In the next chapter we'll add the e-mail body logic and format the data for our mail function.

Top comments (0)