A Mail Command in Python - 02 Adding Flags
Tags: python, beginners, tutorial
Published: 2022-04-20 04:27:29


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!

```python
#!/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()
```

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.

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

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.

```bash
> ./pymail -html
Namespace(html_flag=True)
```

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:
```bash
> ./pymail
Namespace(html_flag=False)
```

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.

```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="")
```
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.

```bash
> ./pymail
Namespace(html_flag=False, subject='')
```

```bash
> ./pymail -s "A subject line"
Namespace(html_flag=False, subject='A subject line')
```

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

```python
    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)
```
This is also pretty straightforward, the only new thing is we have a required keyword that we can set to true.

```bash
> ./pymail
usage: pymail [-h] [-html] [-s SUBJECT] -r FROM_ADDRESS
pymail: error: the following arguments are required: -r/--from-address
```

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

This is great!

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

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.

```python
    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=[])
```

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!

```bash
> ./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')

```

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

```python
    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=[])
```

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.

```python
    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")
```

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:
```bash
> ./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
```

We need to give a destination e-mail address.

```bash
> ./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')
```

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".

```python
    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")
```

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

``` bash
> ./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'])
```

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:

``` python
#!/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()
```

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.