[Tutor] Trouble parsing email from Outlook

Tim Golden mail at timgolden.me.uk
Fri Mar 20 09:57:49 CET 2009


Eduardo Vieira wrote:
> Hello, list! I hope it's not too much out of place to ask this
> question in the Tutor list.
> I'm trying to process some information from email messages that goes
> in a folder called: "SysAdmin". I could reproduce the recipe from
> Python Programming on Win32 sucessfully to read the subject line of
> the inbox, but not from a different folder:

It can be a bit confusing working out even what to search
for, and there are a few things which are similar but
subtly different. Ignoring more esoteric possibilities,
there are three main ways to do automatic things with
Outlook / Exchange emails, all of which are feasible from
Python + pywin32/ctypes/comtypes.

1) COM -> CDO ("MAPI.Session") -- that's what you're doing.
Altho' technically this is using Exchange rather than Outlook,
you basically need Outlook installed for this to work.

2) COM -> Outlook ("Outlook.Application") -- this is very similar
to CDO but gives you a few things which are tied to Outlook
rather than to the Exchange server.

3) COM -> MAPI -- this is a slightly lower-level Win32 API set
which is exposed in pywin32 through the slightly confusing
set of modules under win32comext/mapi

Of course it's all very confusing because the CDO CLSID
above uses the word "MAPI" (as it presumably calls MAPI
functions under the covers) while there's another thing
called CDONT which is/was a cut-down version of CDO and
which you still come across from time to time. 


> So far my code is this:
> import win32com.client
> ses = win32com.client.Dispatch("Mapi.Session")
> o = win32com.client.Dispatch("Outlook.Application")

OK, now you've confused yourself. You don't need to use
*both* Outlook automation *and* CDO automation. Just
pick one. Let's go with CDO since that's effectively
what you've done.

> ses.Logon("Default")
> print ses.Inbox.Messages.Item(1).Subject

Right, because the Inbox is a well-known special case,
you get an easy reference to it from the CDO session
itself. To find other folders, you either have to
walk the tree of folders if you know where to look,
or to iterate over them if you just know it's there
somewhere but can't guarantee where.

The topmost things in a CDO Session is one or more
InfoStores. You can't iterate over them directly;
you have to loop over their count. Note that they
are counted from 1 upwards, while the Python loop
is 0-based:

<code>
for i in range (len (ses.InfoStores)):
  info_store = ses.InfoStores[i+1]
  print info_store.Name

</code>

If you already know the name of the one you want,
eg "Mailbox - Tim Golden", you can select that one:

<code>
mailbox = ses.InfoStores["Mailbox - Tim Golden"]
</code>

The infostore has a RootFolder which is a CDO Folder
object and once you've got that, you can just walk the
tree of folders. The key collections you'll be interested
in are the Folders and Messages. They can both be iterated
the same way, and the function below provides a Pythonish
wrapper:

<code>
def cdo_iter (cdo_collection):
  item = cdo_collection.GetFirst ()
  while item:
    yield item
    item = cdo_collection.GetNext ()

</code>

Each Folder may have a Folders attribute and a Messages
attribute. To walk a tree or subtree, you can do this,
making use of the function above:

<code>
def cdo_walk (folder): ## can be the RootFolder or a specific folder
  try:
    folders = cdo_iter (folder.Folders)
  except AttributeError:
    folders = []
  try:
    items = cdo_iter (folder.Messages)
  except AttributeError:
    items = []

  yield folder, folders, items

  for subfolder in folders:
    for r in cdo_walk (subfolder):
      yield r

</code>

Note that, because we're using generators, the sublists
of folders and items are generated lazily. If, as in the
case we're getting to, you don't need to look at messages
until you've got to the folder you want, then you just don't
iterate over the items iterable.

Putting it together, you can find a folder called "SysAdmin"
from a CDO session like this:

<code>
import os, sys
import win32com.client

def cdo_iter (cdo_collection):
  item = cdo_collection.GetFirst ()
  while item:
    yield item
    item = cdo_collection.GetNext ()

def cdo_walk (folder):
  try:
    folders = cdo_iter (folder.Folders)
  except AttributeError:
    folders = []
  try:
    items = cdo_iter (folder.Messages)
  except AttributeError:
    items = []

  yield folder, folders, items

  for subfolder in folders:
    for r in cdo_walk (subfolder):
      yield r

class x_found (Exception):
  pass
  
if __name__ == '__main__':
  session = win32com.client.gencache.EnsureDispatch ("MAPI.Session")
  session.Logon ()

  try:
    for i in range (session.InfoStores.Count):
      info_store = session.InfoStores[i+1]
      #
      # Ignore Public Folders which is very big
      #
      if info_store.Name == "Public Folders": continue
      print "Searching", info_store.Name
      
      for folder, folders, items in cdo_walk (info_store.RootFolder):
        if folder.Name == "SysAdmin":
          for item in items:
            print item.Subject
          raise x_found
  
  except x_found:
    pass

</code>

TJG


More information about the Tutor mailing list