How can I declare and call a dynamic variable based on other hierarchical variables in Python?

Viewed 117

I'm attempting to write a scraper that will download attachments from an outlook account when I specify the path to folder to download from. I have working code but the folder locations are hardcoded as below:-

import win32com.client

outlook = win32com.client.Dispatch("Outlook.Application")
namespace = outlook.GetNamespace("MAPI")
root_folder = namespace.Folders.Item('Personal') #Inbox of interest
subfolder = root_folder.Folders['Inbox'].Folders['Holidays'].Folders['2009'].Folders['June'] #I have  Holidays,2009 and June folders in that hierarchical order in the  mailbox called Personal
Messages = subfolder.Items

With the code, I'm able to scrape emails and attachments from that inbox based on other preset parameters.

However, I'm interested in doing this dynamically, such that depending on the number of nested folders, I'll be able to build up the subfolder variable as in the above code. I've tried this using the below code.

import win32com.client

outlook = win32com.client.Dispatch("Outlook.Application")
namespace = outlook.GetNamespace("MAPI")            

root_mailbox  = str(input("Please Enter the Root folder name for the mailbox to scrape from:")) #Mailbox  to download from i.e. may be like "Personal" above.
if (root_mailbox == ""):
    print("You cannot leave this empty. Please insert the root folder name.")
else:
    
    root_folder = namespace.Folders.Item(root_mailbox) 
    sub_folders_num = int(input('How  many subfolders  are to be iterated on in the mailbox. Remember this has to be a number.')) #Specify the number of folders

    foldernames = [] #List to append to names values
    for i in range(1,sub_folders_num+1): 
        i = input("Input folder name(s) starting  with the one closest to the root folder") 
        foldernames.append(i)      
                

subfolder = root_folder.Folders[foldernames[0]].Folders[foldernames[1]].Folders[foldernames[2]] # Subfolder path but still "manually" specified 
Messages = subfolder.Items

subfolder variable will be valid but the process is still manual. How can I be able to have "Folders" specified dynamically based on sub_folders_num and the subfolder path built with the list values as captured in the loop? This will help in creating a more dynamic scraper where I can download emails/attachments from any inbox folder path.

2 Answers

You can do this as a reduction over foldernames using getattr to dynamically get the next attribute.

from functools import reduce


subfolder = reduce(lambda x, y: getattr(x, x.Folders[y]), foldernames, root_folder)

Alternatively, a non-functional approach that is less elegant, but easier to debug:

subfolder = root_folder
for folder_name in foldernames:
    subfolder = subfolder.Folders[folder_name]

The end result should be the same as the reduce method.

It seems you need to iterate over all folders in Outlook recursively. Here is a sample VBA macro, so you may see the sequence of property and methods calls to get the job done:

private void EnumerateFoldersInDefaultStore()
{
    Outlook.Folder root =
        Application.Session.
        DefaultStore.GetRootFolder() as Outlook.Folder;
    EnumerateFolders(root);
}

// Uses recursion to enumerate Outlook subfolders.
private void EnumerateFolders(Outlook.Folder folder)
{
    Outlook.Folders childFolders = folder.Folders;
    if (childFolders.Count > 0)
    {
        foreach (Outlook.Folder childFolder in childFolders)
        {
            // Write the folder path.
            Debug.WriteLine(childFolder.FolderPath);
            // Call EnumerateFolders using childFolder.
            EnumerateFolders(childFolder);
        }
    }
}            
Related