Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (142 page)

BOOK: Programming Python
3.35Mb size Format: txt, pdf, ePub
ads

def __init__(self):
PyMailCommon.__init__(self)
self.cache = messagecache.GuiMessageCache() # embedded, not inherited
#self.listBox.insert(END, 'Press Load to fetch mail')
def makeWidgets(self): # help bar: main win only
self.addHelpBar()
PyMailCommon.makeWidgets(self)
def addHelpBar(self):
msg = 'PyMailGUI - a Python/tkinter email client (help)'
title = Button(self, text=msg)
title.config(bg='steelblue', fg='white', relief=RIDGE)
title.config(command=self.onShowHelp)
title.pack(fill=X)
def onShowHelp(self):
"""
load,show text block string
3.0: now uses HTML and webbrowser module here too
user setting in mailconfig selects text, HTML, or both
always displays one or the other: html if both false
"""
if mailconfig.showHelpAsText:
from PyMailGuiHelp import helptext
popuputil.HelpPopup(appname, helptext, showsource=self.onShowMySource)
if mailconfig.showHelpAsHTML or (not mailconfig.showHelpAsText):
from PyMailGuiHelp import showHtmlHelp
showHtmlHelp() # 3.0: HTML version without source file links
def onShowMySource(self, showAsMail=False):
"""
display my sourcecode file, plus imported modules here & elsewhere
"""
import PyMailGui, ListWindows, ViewWindows, SharedNames, textConfig
from PP4E.Internet.Email.mailtools import ( # mailtools now a pkg
mailSender, mailFetcher, mailParser) # can't use * in def
mymods = (
PyMailGui, ListWindows, ViewWindows, SharedNames,
PyMailGuiHelp, popuputil, messagecache, wraplines, html2text,
mailtools, mailFetcher, mailSender, mailParser,
mailconfig, textConfig, threadtools, windows, textEditor)
for mod in mymods:
source = mod.__file__
if source.endswith('.pyc'):
source = source[:-4] + '.py' # assume a .py in same dir
if showAsMail:
# this is a bit cheesey...
code = open(source).read() # 3.0: platform encoding
user = mailconfig.myaddress
hdrmap = {'From': appname, 'To': user, 'Subject': mod.__name__}
ViewWindow(showtext=code,
headermap=hdrmap,
origmessage=email.message.Message())
else:
# more useful PyEdit text editor
# 4E: assume in UTF8 Unicode encoding (else PeEdit may ask!)
wintitle = ' - ' + mod.__name__
textEditor.TextEditorMainPopup(self, source, wintitle, 'utf-8')
def onLoadServer(self, forceReload=False):
"""
threaded: load or reload mail headers list on request;
Exit,Fail,Progress run by threadChecker after callback via queue;
load may overlap with sends, but disables all but send;
could overlap with loadingMsgs, but may change msg cache list;
forceReload on delete/synch fail, else loads recent arrivals only;
2.1: cache.loadHeaders may do quick check to see if msgnums
in synch with server, if we are loading just newly arrived hdrs;
"""
if loadingHdrsBusy or deletingBusy or loadingMsgsBusy:
showerror(appname, 'Cannot load headers during load or delete')
else:
loadingHdrsBusy.incr()
self.cache.setPopPassword(appname) # don't update GUI in the thread!
popup = popuputil.BusyBoxNowait(appname, 'Loading message headers')
threadtools.startThread(
action = self.cache.loadHeaders,
args = (forceReload,),
context = (popup,),
onExit = self.onLoadHdrsExit,
onFail = self.onLoadHdrsFail,
onProgress = self.onLoadHdrsProgress)
def onLoadHdrsExit(self, popup):
self.fillIndex()
popup.quit()
self.lift()
loadingHdrsBusy.decr() # allow other actions to run
def onLoadHdrsFail(self, exc_info, popup):
popup.quit()
showerror(appname, 'Load failed: \n%s\n%s' % exc_info[:2])
printStack(exc_info) # send stack trace to stdout
loadingHdrsBusy.decr()
if exc_info[0] == mailtools.MessageSynchError: # synch inbox/index
self.onLoadServer(forceReload=True) # new thread: reload
else:
self.cache.popPassword = None # force re-input next time
def onLoadHdrsProgress(self, i, n, popup):
popup.changeText('%d of %d' % (i, n))
def doDelete(self, msgnumlist):
"""
threaded: delete from server now - changes msg nums;
may overlap with sends only, disables all except sends;
2.1: cache.deleteMessages now checks TOP result to see
if headers match selected mails, in case msgnums out of
synch with mail server: poss if mail deleted by other client,
or server deletes inbox mail automatically - some ISPs may
move a mail from inbox to undeliverable on load failure;
"""
if loadingHdrsBusy or deletingBusy or loadingMsgsBusy:
showerror(appname, 'Cannot delete during load or delete')
else:
deletingBusy.incr()
popup = popuputil.BusyBoxNowait(appname, 'Deleting selected mails')
threadtools.startThread(
action = self.cache.deleteMessages,
args = (msgnumlist,),
context = (popup,),
onExit = self.onDeleteExit,
onFail = self.onDeleteFail,
onProgress = self.onDeleteProgress)
def onDeleteExit(self, popup):
self.fillIndex() # no need to reload from server
popup.quit() # refill index with updated cache
self.lift() # raise index window, release lock
deletingBusy.decr()
def onDeleteFail(self, exc_info, popup):
popup.quit()
showerror(appname, 'Delete failed: \n%s\n%s' % exc_info[:2])
printStack(exc_info)
deletingBusy.decr() # delete or synch check failure
self.onLoadServer(forceReload=True) # new thread: some msgnums changed
def onDeleteProgress(self, i, n, popup):
popup.changeText('%d of %d' % (i, n))
def getMessages(self, msgnums, after):
"""
threaded: prefetch all selected messages into cache now;
used by save, view, reply, and forward to prefill cache;
may overlap with other loadmsgs and sends, disables delete,load;
only runs "after" action if the fetch allowed and successful;
2.1: cache.getMessages tests if index in synch with server,
but we only test if we have to go to server, not if cached;
3.0: see messagecache note: now avoids potential fetch of mail
currently being fetched, if user clicks again while in progress;
any message being fetched by any other request in progress must
disable entire toLoad batch: else, need to wait for N other loads;
fetches are still allowed to overlap in time, as long as disjoint;
"""
if loadingHdrsBusy or deletingBusy:
showerror(appname, 'Cannot fetch message during load or delete')
else:
toLoad = [num for num in msgnums if not self.cache.isLoaded(num)]
if not toLoad:
after() # all already loaded
return # process now, no wait pop up
else:
if set(toLoad) & self.beingFetched: # 3.0: any in progress?
showerror(appname, 'Cannot fetch any message being fetched')
else:
self.beingFetched |= set(toLoad)
loadingMsgsBusy.incr()
from popuputil import BusyBoxNowait
popup = BusyBoxNowait(appname, 'Fetching message contents')
threadtools.startThread(
action = self.cache.getMessages,
args = (toLoad,),
context = (after, popup, toLoad),
onExit = self.onLoadMsgsExit,
onFail = self.onLoadMsgsFail,
onProgress = self.onLoadMsgsProgress)
def onLoadMsgsExit(self, after, popup, toLoad):
self.beingFetched -= set(toLoad)
popup.quit()
after()
loadingMsgsBusy.decr() # allow other actions after onExit done
def onLoadMsgsFail(self, exc_info, after, popup, toLoad):
self.beingFetched -= set(toLoad)
popup.quit()
showerror(appname, 'Fetch failed: \n%s\n%s' % exc_info[:2])
printStack(exc_info)
loadingMsgsBusy.decr()
if exc_info[0] == mailtools.MessageSynchError: # synch inbox/index
self.onLoadServer(forceReload=True) # new thread: reload
def onLoadMsgsProgress(self, i, n, after, popup, toLoad):
popup.changeText('%d of %d' % (i, n))
def getMessage(self, msgnum):
return self.cache.getMessage(msgnum) # full mail text
def headersMaps(self):
# list of email.message.Message objects, 3.x requires list() if map()
# return [self.parseHeaders(h) for h in self.cache.allHdrs()]
return list(map(self.parseHeaders, self.cache.allHdrs()))
def mailSize(self, msgnum):
return self.cache.getSize(msgnum)
def okayToQuit(self):
# any threads still running?
filesbusy = [win for win in openSaveFiles.values() if win.openFileBusy]
busy = loadingHdrsBusy or deletingBusy or sendingBusy or loadingMsgsBusy
busy = busy or filesbusy
return not busy
ViewWindows: Message View Windows

Example 14-4
lists
the implementation of mail view and edit windows. These
windows are created in response to actions in list windows—View, Write,
Reply, and Forward buttons. See the callbacks for these actions in the
list window module of
Example 14-3
for view window
initiation calls.

As in the prior module (
Example 14-3
), this file is really
one common class and a handful of customizations. The mail view window
is nearly identical to the mail edit window, used for Write, Reply, and
Forward requests. Consequently, this example defines the common
appearance and behavior in the view window superclass, and extends it by
subclassing for edit windows.

Replies and forwards are hardly different from the write window
here, because their details (e.g., From and To addresses and quoted
message text) are worked out in the list window implementation before an
edit window is created.

Example 14-4. PP4E\Internet\Email\PyMailGui\ViewWindows.py

"""
###############################################################################
Implementation of View, Write, Reply, Forward windows: one class per kind.
Code is factored here for reuse: a Write window is a customized View window,
and Reply and Forward are custom Write windows. Windows defined in this
file are created by the list windows, in response to user actions.
Caveat:'split' pop ups for opening parts/attachments feel nonintuitive.
2.1: this caveat was addressed, by adding quick-access attachment buttons.
New in 3.0: platform-neutral grid() for mail headers, not packed col frames.
New in 3.0: supports Unicode encodings for main text + text attachments sent.
New in 3.0: PyEdit supports arbitrary Unicode for message parts viewed.
New in 3.0: supports Unicode/mail encodings for headers in mails sent.
TBD: could avoid verifying quits unless text area modified (like PyEdit2.0),
but these windows are larger, and would not catch headers already changed.
TBD: should Open dialog in write windows be program-wide? (per-window now).
###############################################################################
"""
from SharedNames import * # program-wide global objects
###############################################################################
# message view window - also a superclass of write, reply, forward
###############################################################################
class ViewWindow(windows.PopupWindow, mailtools.MailParser):
"""
a Toplevel, with extra protocol and embedded TextEditor;
inherits saveParts,partsList from mailtools.MailParser;
mixes in custom subclass logic by direct inheritance here;
"""
# class attributes
modelabel = 'View' # used in window titles
from mailconfig import okayToOpenParts # open any attachments at all?
from mailconfig import verifyPartOpens # ask before open each part?
from mailconfig import maxPartButtons # show up to this many + '...'
from mailconfig import skipTextOnHtmlPart # 3.0: just browser, not PyEdit?
tempPartDir = 'TempParts' # where 1 selected part saved
# all view windows use same dialog: remembers last dir
partsDialog = Directory(title=appname + ': Select parts save directory')
def __init__(self, headermap, showtext, origmessage=None):
"""
header map is origmessage, or custom hdr dict for writing;
showtext is main text part of the message: parsed or custom;
origmessage is parsed email.message.Message for view mail windows
"""
windows.PopupWindow.__init__(self, appname, self.modelabel)
self.origMessage = origmessage
self.makeWidgets(headermap, showtext)
def makeWidgets(self, headermap, showtext):
"""
add headers, actions, attachments, text editor
3.0: showtext is assumed to be decoded Unicode str here;
it will be encoded on sends and saves as directed/needed;
"""
actionsframe = self.makeHeaders(headermap)
if self.origMessage and self.okayToOpenParts:
self.makePartButtons()
self.editor = textEditor.TextEditorComponentMinimal(self)
myactions = self.actionButtons()
for (label, callback) in myactions:
b = Button(actionsframe, text=label, command=callback)
b.config(bg='beige', relief=RIDGE, bd=2)
b.pack(side=TOP, expand=YES, fill=BOTH)
# body text, pack last=clip first
self.editor.pack(side=BOTTOM) # may be multiple editors
self.update() # 3.0: else may be @ line2
self.editor.setAllText(showtext) # each has own content
lines = len(showtext.splitlines())
lines = min(lines + 3, mailconfig.viewheight or 20)
self.editor.setHeight(lines) # else height=24, width=80
self.editor.setWidth(80) # or from PyEdit textConfig
if mailconfig.viewbg:
self.editor.setBg(mailconfig.viewbg) # colors, font in mailconfig
if mailconfig.viewfg:
self.editor.setFg(mailconfig.viewfg)
if mailconfig.viewfont: # also via editor Tools menu
self.editor.setFont(mailconfig.viewfont)
def makeHeaders(self, headermap):
"""
add header entry fields, return action buttons frame;
3.0: uses grid for platform-neutral layout of label/entry rows;
packed row frames with fixed-width labels would work well too;
3.0: decoding of i18n headers (and email names in address headers)
is performed here if still required as they are added to the GUI;
some may have been decoded already for reply/forward windows that
need to use decoded text, but the extra decode here is harmless for
these, and is required for other headers and cases such as fetched
mail views; always, headers are in decoded form when displayed in
the GUI, and will be encoded within mailtools on Sends if they are
non-ASCII (see Write); i18n header decoding also occurs in list
window mail indexes, and for headers added to quoted mail text;
text payloads in the mail body are also decoded for display and
encoded for sends elsewhere in the system (list windows, Write);
3.0: creators of edit windows prefill Bcc header with sender email
address to be picked up here, as a convenience for common usages if
this header is enabled in mailconfig; Reply also now prefills the
Cc header with all unique original recipients less From, if enabled;
"""
top = Frame(self); top.pack (side=TOP, fill=X)
left = Frame(top); left.pack (side=LEFT, expand=NO, fill=BOTH)
middle = Frame(top); middle.pack(side=LEFT, expand=YES, fill=X)
# headers set may be extended in mailconfig (Bcc, others?)
self.userHdrs = ()
showhdrs = ('From', 'To', 'Cc', 'Subject')
if hasattr(mailconfig, 'viewheaders') and mailconfig.viewheaders:
self.userHdrs = mailconfig.viewheaders
showhdrs += self.userHdrs
addrhdrs = ('From', 'To', 'Cc', 'Bcc') # 3.0: decode i18n specially
self.hdrFields = []
for (i, header) in enumerate(showhdrs):
lab = Label(middle, text=header+':', justify=LEFT)
ent = Entry(middle)
lab.grid(row=i, column=0, sticky=EW)
ent.grid(row=i, column=1, sticky=EW)
middle.rowconfigure(i, weight=1)
hdrvalue = headermap.get(header, '?') # might be empty
# 3.0: if encoded, decode per email+mime+unicode
if header not in addrhdrs:
hdrvalue = self.decodeHeader(hdrvalue)
else:
hdrvalue = self.decodeAddrHeader(hdrvalue)
ent.insert('0', hdrvalue)
self.hdrFields.append(ent) # order matters in onSend
middle.columnconfigure(1, weight=1)
return left
def actionButtons(self): # must be method for self
return [('Cancel', self.destroy), # close view window silently
('Parts', self.onParts), # multiparts list or the body
('Split', self.onSplit)]
def makePartButtons(self):
"""
add up to N buttons that open attachments/parts
when clicked; alternative to Parts/Split (2.1);
okay that temp dir is shared by all open messages:
part file not saved till later selected and opened;
partname=partname is required in lambda in Py2.4;
caveat: we could try to skip the main text part;
"""
def makeButton(parent, text, callback):
link = Button(parent, text=text, command=callback, relief=SUNKEN)
if mailconfig.partfg: link.config(fg=mailconfig.partfg)
if mailconfig.partbg: link.config(bg=mailconfig.partbg)
link.pack(side=LEFT, fill=X, expand=YES)
parts = Frame(self)
parts.pack(side=TOP, expand=NO, fill=X)
for (count, partname) in enumerate(self.partsList(self.origMessage)):
if count == self.maxPartButtons:
makeButton(parts, '...', self.onSplit)
break
openpart = (lambda partname=partname: self.onOnePart(partname))
makeButton(parts, partname, openpart)
def onOnePart(self, partname):
"""
locate selected part for button and save and open;
okay if multiple mails open: resaves each time selected;
we could probably just use web browser directly here;
caveat: tempPartDir is relative to cwd - poss anywhere;
caveat: tempPartDir is never cleaned up: might be large,
could use tempfile module (just like the HTML main text
part display code in onView of the list window class);
"""
try:
savedir = self.tempPartDir
message = self.origMessage
(contype, savepath) = self.saveOnePart(savedir, partname, message)
except:
showerror(appname, 'Error while writing part file')
printStack(sys.exc_info())
else:
self.openParts([(contype, os.path.abspath(savepath))]) # reuse
def onParts(self):
"""
show message part/attachments in pop-up window;
uses same file naming scheme as save on Split;
if non-multipart, single part = full body text
"""
partnames = self.partsList(self.origMessage)
msg = '\n'.join(['Message parts:\n'] + partnames)
showinfo(appname, msg)
def onSplit(self):
"""
pop up save dir dialog and save all parts/attachments there;
if desired, pop up HTML and multimedia parts in web browser,
text in TextEditor, and well-known doc types on windows;
could show parts in View windows where embedded text editor
would provide a save button, but most are not readable text;
"""
savedir = self.partsDialog.show() # class attr: at prior dir
if savedir: # tk dir chooser, not file
try:
partfiles = self.saveParts(savedir, self.origMessage)
except:
showerror(appname, 'Error while writing part files')
printStack(sys.exc_info())
else:
if self.okayToOpenParts: self.openParts(partfiles)
def askOpen(self, appname, prompt):
if not self.verifyPartOpens:
return True
else:
return askyesno(appname, prompt) # pop-up dialog
def openParts(self, partfiles):
"""
auto-open well known and safe file types, but only if verified
by the user in a pop up; other types must be opened manually
from save dir; at this point, the named parts have been already
MIME-decoded and saved as raw bytes in binary-mode files, but text
parts may be in any Unicode encoding; PyEdit needs to know the
encoding to decode, webbrowsers may have to guess or be told;
caveat: punts for type application/octet-stream even if it has
safe filename extension such as .html; caveat: image/audio/video
could be opened with the book's playfile.py; could also do that
if text viewer fails: would start notepad on Windows via startfile;
webbrowser may handle most cases here too, but specific is better;
"""
def textPartEncoding(fullfilename):
"""
3.0: map a text part filename back to charset param in content-type
header of part's Message, so we can pass this on to the PyEdit
constructor for proper text display; we could return the charset
along with content-type from mailtools for text parts, but fewer
changes are needed if this is handled as a special case here;
part content is saved in binary mode files by mailtools to avoid
encoding issues, but here the original part Message is not directly
available; we need this mapping step to extract a Unicode encoding
name if present; 4E's PyEdit now allows an explicit encoding name for
file opens, and resolves encoding on saves; see Chapter 11 for PyEdit
policies: it may ask user for an encoding if charset absent or fails;
caveat: move to mailtools.mailParser to reuse for in PyMailCGI?
"""
partname = os.path.basename(fullfilename)
for (filename, contype, part) in self.walkNamedParts(self.origMessage):
if filename == partname:
return part.get_content_charset() # None if not in header
assert False, 'Text part not found' # should never happen
for (contype, fullfilename) in partfiles:
maintype = contype.split('/')[0] # left side
extension = os.path.splitext(fullfilename)[1] # not [-4:]
basename = os.path.basename(fullfilename) # strip dir
# HTML and XML text, web pages, some media
if contype in ['text/html', 'text/xml']:
browserOpened = False
if self.askOpen(appname, 'Open "%s" in browser?' % basename):
try:
webbrowser.open_new('file://' + fullfilename)
browserOpened = True
except:
showerror(appname, 'Browser failed: trying editor')
if not browserOpened or not self.skipTextOnHtmlPart:
try:
# try PyEdit to see encoding name and effect
encoding = textPartEncoding(fullfilename)
textEditor.TextEditorMainPopup(parent=self,
winTitle=' - %s email part' % (encoding or '?'),
loadFirst=fullfilename, loadEncode=encoding)
except:
showerror(appname, 'Error opening text viewer')
# text/plain, text/x-python, etc.; 4E: encoding, may fail
elif maintype == 'text':
if self.askOpen(appname, 'Open text part "%s"?' % basename):
try:
encoding = textPartEncoding(fullfilename)
textEditor.TextEditorMainPopup(parent=self,
winTitle=' - %s email part' % (encoding or '?'),
loadFirst=fullfilename, loadEncode=encoding)
except:
showerror(appname, 'Error opening text viewer')
# multimedia types: Windows opens mediaplayer, imageviewer, etc.
elif maintype in ['image', 'audio', 'video']:
if self.askOpen(appname, 'Open media part "%s"?' % basename):
try:
webbrowser.open_new('file://' + fullfilename)
except:
showerror(appname, 'Error opening browser')
# common Windows documents: Word, Excel, Adobe, archives, etc.
elif (sys.platform[:3] == 'win' and
maintype == 'application' and # 3.0: +x types
extension in ['.doc', '.docx', '.xls', '.xlsx', # generalize me
'.pdf', '.zip', '.tar', '.wmv']):
if self.askOpen(appname, 'Open part "%s"?' % basename):
os.startfile(fullfilename)
else: # punt!
msg = 'Cannot open part: "%s"\nOpen manually in: "%s"'
msg = msg % (basename, os.path.dirname(fullfilename))
showinfo(appname, msg)
###############################################################################
# message edit windows - write, reply, forward
###############################################################################
if mailconfig.smtpuser: # user set in mailconfig?
MailSenderClass = mailtools.MailSenderAuth # login/password required
else:
MailSenderClass = mailtools.MailSender
class WriteWindow(ViewWindow, MailSenderClass):
"""
customize view display for composing new mail
inherits sendMessage from mailtools.MailSender
"""
modelabel = 'Write'
def __init__(self, headermap, starttext):
ViewWindow.__init__(self, headermap, starttext)
MailSenderClass.__init__(self)
self.attaches = [] # each win has own open dialog
self.openDialog = None # dialog remembers last dir
BOOK: Programming Python
3.35Mb size Format: txt, pdf, ePub
ads

Other books

Smugglers! by Karen King
Enthusiasm by Polly Shulman
Ojalá estuvieras aquí by Francesc Miralles
The Bride Tamer by Ann Major
So It Begins by Mike McPhail (Ed)