[Tutor] Use python to parse the subject line of emails, listen for and react to commands

Danny Yoo dyoo at hashcollision.org
Tue Mar 3 00:19:18 CET 2015


Hi Willie,

Ok, spent a few minutes looking over the code.

I'll spend a brief moment reviewing the following block:

>         msg = email.message_from_string(data[0][1])
>         decode = email.header.decode_header(msg['Subject'])[0]
>         subject = unicode(decode[0])
>         print '%s' % (subject)
>         COMMAND_FILE.write('%s' % (subject)+'.py')
>         COMMAND_FILE.close()
>         EX = '%s' %(subject)+'.py' #save the subject as an name.py
>         execfile (EX) # exe name as a py


1.  Do you have a complete list of all the command files you're planning to use?


2.  The use of string formatting here might be redundant.  Instead of:

    print '%s' % (subject)

since we know subject is a unicode string, can we just say:

    print subject


3.  Similar comment on the line:

    COMMAND_FILE.write('%s' % (subject)+'.py')

but somewhat different.  If you're doing string formatting, be
consistent: do it so we can see the shape of the final string by
looking at the string literal alone.   Here, the concatenation of
'.py' is apart from the string literal.  It should be together.

    COMMAND_FILE.write('%s.py' % (subject))

My comment here, though, might be overridden by the recommendation to
avoid use of unrestricted execfile in favor of a closed approach.


4.  If you have a closed set of command names, then you can do
something significantly safer here.  Concretely, let's say that you
have the following three commands:

    "open", "reply", "ping"

I'm just making these up so that we have something concrete to talk about.


Let us assume that we have, corresponding to these command names, the
three modules: "open_command.py", "reply_command.py" and
"ping_command.py".


But unlike what you probably have now, let's say that each of these
modules defines a run() function.

#####################
## open.py
def run():
    print "This is open."


## reply.py
def run():
    print "This is reply."


## ping.py
def run():
    print "This is ping."
#####################


If we have this organization, then in your main command driver, you
can safely construct a mapping from command name to the appropriate
run() function:

########################################################
import open_command
import reply_command
import ping_command


COMMANDS = {
    u'open' : open_command.run,
    u'reply' : reply_command.run,
    u'ping' : ping_command.run,
}


# ... later in your program ...
def process_mailbox(M):
    # ...

    msg = email.message_from_string(data[0][1])
    decode = email.header.decode_header(msg['Subject'])[0]
    subject = unicode(decode[0])

    ## here's where things are slightly different:
    command = COMMANDS[subject]
    command()
#########################################################


There are a few things to note there:

1.  This approach is much safer because we know that the only commands
that can be run are listed in COMMANDS.  It's a lot more constrained
in what it can do.

2.  It's also robust in the face of changes to the current directory.
If your current directory is somewhere where you can't write, that has
no effect whatsover with this revision.  The original approach can
suffer from mysterious failure possibilities because it implicitly
depends on the "global" current working directory.

3.  Other folks will better understand what's going on: this
command-dispatching pattern is used quite often.  For example, see:

    https://docs.python.org/2/faq/design.html#why-isn-t-there-a-switch-or-case-statement-in-python


There are also some guaranteed low-level efficiency bonuses if we take
this approach.  But those are ancillary when we compare vs the safety
guarantees we'll get if we avoid exec or execfile approaches.


If you have questions about this, please ask, and hopefully someone
here can help explain further and point to resources.  Good luck!


More information about the Tutor mailing list