The last file in
themailtools
package,
Example 13-26
, lists the
self-test code for the package. This code is a separate script file, in
order to allow for import search path manipulation—it emulates a real
client, which is assumed to have amailconfig.py
module in its own source
directory (this module can vary per client).
Example 13-26. PP4E\Internet\Email\mailtools\selftest.py
"""
###############################################################################
self-test when this file is run as a program
###############################################################################
"""
#
# mailconfig normally comes from the client's source directory or
# sys.path; for testing, get it from Email directory one level up
#
import sys
sys.path.append('..')
import mailconfig
print('config:', mailconfig.__file__)
# get these from __init__
from mailtools import (MailFetcherConsole,
MailSender, MailSenderAuthConsole,
MailParser)
if not mailconfig.smtpuser:
sender = MailSender(tracesize=5000)
else:
sender = MailSenderAuthConsole(tracesize=5000)
sender.sendMessage(From = mailconfig.myaddress,
To = [mailconfig.myaddress],
Subj = 'testing mailtools package',
extrahdrs = [('X-Mailer', 'mailtools')],
bodytext = 'Here is my source code\n',
attaches = ['selftest.py'],
)
# bodytextEncoding='utf-8', # other tests to try
# attachesEncodings=['latin-1'], # inspect text headers
# attaches=['monkeys.jpg']) # verify Base64 encoded
# to='i18n adddr list...', # test mime/unicode headers
# change mailconfig to test fetchlimit
fetcher = MailFetcherConsole()
def status(*args): print(args)
hdrs, sizes, loadedall = fetcher.downloadAllHeaders(status)
for num, hdr in enumerate(hdrs[:5]):
print(hdr)
if input('load mail?') in ['y', 'Y']:
print(fetcher.downloadMessage(num+1).rstrip(), '\n', '-'*70)
last5 = len(hdrs)-4
msgs, sizes, loadedall = fetcher.downloadAllMessages(status, loadfrom=last5)
for msg in msgs:
print(msg[:200], '\n', '-'*70)
parser = MailParser()
for i in [0]: # try [0 , len(msgs)]
fulltext = msgs[i]
message = parser.parseMessage(fulltext)
ctype, maintext = parser.findMainText(message)
print('Parsed:', message['Subject'])
print(maintext)
input('Press Enter to exit') # pause if clicked on Windows
Here’s a run of the self-test script; it generates a lot of
output, most of which has been deleted here for presentation in this
book—as usual, run this on your own for further details:
C:\...\PP4E\Internet\Email\mailtools>selftest.py
config: ..\mailconfig.py
user: [email protected]
Adding text/x-python
Sending to...['[email protected]']
Content-Type: multipart/mixed; boundary="===============0085314748=="
MIME-Version: 1.0
From: [email protected]
To: [email protected]
Subject: testing mailtools package
Date: Sat, 08 May 2010 19:26:22 −0000
X-Mailer: mailtools
A multi-part MIME format message.
--===============0085314748==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Here is my source code
--===============0085314748==
Content-Type: text/x-python; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="selftest.py"
"""
###############################################################################
self-test when this file is run as a program
###############################################################################
"""
...more lines omitted...
print(maintext)
input('Press Enter to exit') # pause if clicked on Windows
--===============0085314748==--
Send exit
loading headers
Connecting...
Password for [email protected] on pop.secureserver.net?
b'+OK <[email protected]>'
(1, 7)
(2, 7)
(3, 7)
(4, 7)
(5, 7)
(6, 7)
(7, 7)
load headers exit
Received: (qmail 7690 invoked from network); 5 May 2010 15:29:43 −0000
Received: from unknown (HELO p3pismtp01-026.prod.phx3.secureserver.net) ([10.6.1
...more lines omitted...
load mail?y
load 1
Connecting...
b'+OK <[email protected]>'
Received: (qmail 7690 invoked from network); 5 May 2010 15:29:43 −0000
Received: from unknown (HELO p3pismtp01-026.prod.phx3.secureserver.net) ([10.6.1
...more lines omitted...
load mail?
loading full messages
Connecting...
b'+OK <[email protected]>'
(3, 7)
(4, 7)
(5, 7)
(6, 7)
(7, 7)
Received: (qmail 25683 invoked from network); 6 May 2010 14:12:07 −0000
Received: from unknown (HELO p3pismtp01-018.prod.phx3.secureserver.net) ([10.6.1
...more lines omitted...
Parsed: A B C D E F G
Fiddle de dum, Fiddle de dee,
Eric the half a bee.
Press Enter to exit
As a final
email example in this chapter, and to give a better use
case for themailtools
module package
of the preceding sections,
Example 13-27
provides an updated
version of thepymail
program we met
earlier (
Example 13-20
). It uses
ourmailtools
package to access
email, instead of interfacing with Python’semail
package directly. Compare its code to
the originalpymail
in this chapter
to see howmailtools
is employed
here. You’ll find that its mail download and send logic is substantially
simpler.
Example 13-27. PP4E\Internet\Email\pymail2.py
#!/usr/local/bin/python
"""
################################################################################
pymail2 - simple console email interface client in Python; this version uses
the mailtools package, which in turn uses poplib, smtplib, and the email package
for parsing and composing emails; displays first text part of mails, not the
entire full text; fetches just mail headers initially, using the TOP command;
fetches full text of just email selected to be displayed; caches already
fetched mails; caveat: no way to refresh index; uses standalone mailtools
objects - they can also be used as superclasses;
################################################################################
"""
import mailconfig, mailtools
from pymail import inputmessage
mailcache = {}
def fetchmessage(i):
try:
fulltext = mailcache[i]
except KeyError:
fulltext = fetcher.downloadMessage(i)
mailcache[i] = fulltext
return fulltext
def sendmessage():
From, To, Subj, text = inputmessage()
sender.sendMessage(From, To, Subj, [], text, attaches=None)
def deletemessages(toDelete, verify=True):
print('To be deleted:', toDelete)
if verify and input('Delete?')[:1] not in ['y', 'Y']:
print('Delete cancelled.')
else:
print('Deleting messages from server...')
fetcher.deleteMessages(toDelete)
def showindex(msgList, msgSizes, chunk=5):
count = 0
for (msg, size) in zip(msgList, msgSizes): # email.message.Message, int
count += 1 # 3.x iter ok here
print('%d:\t%d bytes' % (count, size))
for hdr in ('From', 'To', 'Date', 'Subject'):
print('\t%-8s=>%s' % (hdr, msg.get(hdr, '(unknown)')))
if count % chunk == 0:
input('[Press Enter key]') # pause after each chunk
def showmessage(i, msgList):
if 1 <= i <= len(msgList):
fulltext = fetchmessage(i)
message = parser.parseMessage(fulltext)
ctype, maintext = parser.findMainText(message)
print('-' * 79)
print(maintext.rstrip() + '\n') # main text part, not entire mail
print('-' * 79) # and not any attachments after
else:
print('Bad message number')
def savemessage(i, mailfile, msgList):
if 1 <= i <= len(msgList):
fulltext = fetchmessage(i)
savefile = open(mailfile, 'a', encoding=mailconfig.fetchEncoding) # 4E
savefile.write('\n' + fulltext + '-'*80 + '\n')
else:
print('Bad message number')
def msgnum(command):
try:
return int(command.split()[1])
except:
return −1 # assume this is bad
helptext = """
Available commands:
i - index display
l n? - list all messages (or just message n)
d n? - mark all messages for deletion (or just message n)
s n? - save all messages to a file (or just message n)
m - compose and send a new mail message
q - quit pymail
? - display this help text
"""
def interact(msgList, msgSizes, mailfile):
showindex(msgList, msgSizes)
toDelete = []
while True:
try:
command = input('[Pymail] Action? (i, l, d, s, m, q, ?) ')
except EOFError:
command = 'q'
if not command: command = '*'
if command == 'q': # quit
break
elif command[0] == 'i': # index
showindex(msgList, msgSizes)
elif command[0] == 'l': # list
if len(command) == 1:
for i in range(1, len(msgList)+1):
showmessage(i, msgList)
else:
showmessage(msgnum(command), msgList)
elif command[0] == 's': # save
if len(command) == 1:
for i in range(1, len(msgList)+1):
savemessage(i, mailfile, msgList)
else:
savemessage(msgnum(command), mailfile, msgList)
elif command[0] == 'd': # mark for deletion later
if len(command) == 1: # 3.x needs list(): iter
toDelete = list(range(1, len(msgList)+1))
else:
delnum = msgnum(command)
if (1 <= delnum <= len(msgList)) and (delnum not in toDelete):
toDelete.append(delnum)
else:
print('Bad message number')
elif command[0] == 'm': # send a new mail via SMTP
try:
sendmessage()
except:
print('Error - mail not sent')
elif command[0] == '?':
print(helptext)
else:
print('What? -- type "?" for commands help')
return toDelete
def main():
global parser, sender, fetcher
mailserver = mailconfig.popservername
mailuser = mailconfig.popusername
mailfile = mailconfig.savemailfile
parser = mailtools.MailParser()
sender = mailtools.MailSender()
fetcher = mailtools.MailFetcherConsole(mailserver, mailuser)
def progress(i, max):
print(i, 'of', max)
hdrsList, msgSizes, ignore = fetcher.downloadAllHeaders(progress)
msgList = [parser.parseHeaders(hdrtext) for hdrtext in hdrsList]
print('[Pymail email client]')
toDelete = interact(msgList, msgSizes, mailfile)
if toDelete: deletemessages(toDelete)
if __name__ == '__main__': main()
This program is
used interactively, the same as the original. In fact,
the output is nearly identical, so we won’t go into further details.
Here’s a quick look at this script in action; run this on your own
machine to see it firsthand:
C:\...\PP4E\Internet\Email>pymail2.py
user: [email protected]
loading headers
Connecting...
Password for [email protected] on pop.secureserver.net?
b'+OK <[email protected]>'
1 of 7
2 of 7
3 of 7
4 of 7
5 of 7
6 of 7
7 of 7
load headers exit
[Pymail email client]
1: 1860 bytes
From =>[email protected]
To =>[email protected]
Date =>Wed, 5 May 2010 11:29:36 −0400 (EDT)
Subject =>I'm a Lumberjack, and I'm Okay
2: 1408 bytes
From =>[email protected]
To =>[email protected]
Date =>Wed, 05 May 2010 08:33:47 −0700
Subject =>testing
3: 1049 bytes
From =>[email protected]
To =>[email protected]
Date =>Thu, 06 May 2010 14:11:07 −0000
Subject =>A B C D E F G
4: 1038 bytes
From =>[email protected]
To =>[email protected]
Date =>Thu, 06 May 2010 14:32:32 −0000
Subject =>a b c d e f g
5: 957 bytes
From =>[email protected]
To =>maillist
Date =>Thu, 06 May 2010 10:58:40 −0400
Subject =>test interactive smtplib
[Press Enter key]
6: 1037 bytes
From =>[email protected]
To =>[email protected]
Date =>Fri, 07 May 2010 20:32:38 −0000
Subject =>Among our weapons are these
7: 3248 bytes
From =>[email protected]
To =>[email protected]
Date =>Sat, 08 May 2010 19:26:22 −0000
Subject =>testing mailtools package
[Pymail] Action? (i, l, d, s, m, q, ?)l 7
load 7
Connecting...
b'+OK <[email protected]>'
-------------------------------------------------------------------------------
Here is my source code
-------------------------------------------------------------------------------
[Pymail] Action? (i, l, d, s, m, q, ?)d 7
[Pymail] Action? (i, l, d, s, m, q, ?)m
From?[email protected]
To?[email protected]
Subj?test pymail2 send
Type message text, end with line="."Run away! Run away!
.
Sending to...['[email protected]']
From: [email protected]
To: [email protected]
Subject: test pymail2 send
Date: Sat, 08 May 2010 19:44:25 −0000
Run away! Run away!
Send exit
[Pymail] Action? (i, l, d, s, m, q, ?)q
To be deleted: [7]
Delete?y
Deleting messages from server...
deleting mails
Connecting...
b'+OK <[email protected]>'
The messages in our mailbox have quite a few origins now—ISP
webmail clients, basic SMTP scripts, the Python interactive command
line,mailtools
self-test code, and
two console-based email clients; in later chapters, we’ll add even
more. All their mails look the same to our script; here’s a
verification of the email we just sent (the second fetch finds it
already in-cache):
C:\...\PP4E\Internet\Email>pymail2.py
user: [email protected]
loading headers
Connecting...
...more lines omitted...
[Press Enter key]
6: 1037 bytes
From =>[email protected]
To =>[email protected]
Date =>Fri, 07 May 2010 20:32:38 −0000
Subject =>Among our weapons are these
7: 984 bytes
From =>[email protected]
To =>[email protected]
Date =>Sat, 08 May 2010 19:44:25 −0000
Subject =>test pymail2 send
[Pymail] Action? (i, l, d, s, m, q, ?)l 7
load 7
Connecting...
b'+OK <[email protected]>'
-------------------------------------------------------------------------------
Run away! Run away!
-------------------------------------------------------------------------------
[Pymail] Action? (i, l, d, s, m, q, ?)l 7
-------------------------------------------------------------------------------
Run away! Run away!
-------------------------------------------------------------------------------
[Pymail] Action? (i, l, d, s, m, q, ?)q
Studypymail2
’s code for more
insights. As you’ll see, this version eliminates some complexities,
such as the manual formatting of composed mail message text. It also
does a better job of displaying a mail’s text—instead of blindly
listing the full mail text (attachments and all), it usesmailtools
to fetch the first text part of
the message. The messages we’re using are too simple to show the
difference, but for a mail with attachments, this new version will be
more focused about what it displays.
Moreover, because the interface to mail is encapsulated in themailtools
package’s modules, if it
ever must change, it will only need to be changed in that module,
regardless of how many mail clients use its tools. And because the
code inmailtools
is shared, if we
know it works for one client, we can be sure it will work in another;
there is no need to debug new code.
On the other hand,pymail2
doesn’t really leverage much of the power of eithermailtools
or the underlyingemail
package it uses. For example, things
like attachments, Internationalized headers, and inbox synchronization
are not handled at all, and printing of some decoded main text may
contain character sets incompatible with the console terminal
interface. To see the full scope of theemail
package, we need to explore a larger
email system, such as PyMailGUI or PyMailCGI. The first of these is
the topic of the next chapter, and the second appears in
Chapter 16
. First, though, let’s quickly survey
a handful of additional client-side protocol
tools.