Loop through a spreadsheet - python

I made a Python program using tkinter and pandas to select rows and send them by email.
The program let the user decides on which excel file wants to operate;
then asks on which sheet of that file you want to operate;
then it asks how many rows you want to select (using .tail function);
then the program is supposed to iterate through rows and read from a cell (within selected rows) the email address;
then it sends the correct row to the correct address.
I'm stuck at the iteration process.
Here's the code:
import pandas as pd
import smtplib
def invio_mail(my_tailed__df): #the function imports the sliced (.tail) dataframe
gmail_user = '###'
gmail_password = '###'
sent_from = gmail_user
server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
server.ehlo()
server.login(gmail_user, gmail_password)
list = my_tailed_df
customers = list['CUSTOMER']
wrs = list['WR']
phones = list['PHONE']
streets = list['STREET']
cities = list["CITY"]
mobiles = list['MOBILE']
mobiles2 = list['MOBILE2']
mails = list['INST']
for i in range(len(mails)):
customer = customers[i]
wr = wrs[i]
phone = phones[i]
street = streets[i]
city = cities[i]
mobile = mobiles[i]
mobile2 = mobiles2[i]
"""
for i in range(len(mails)):
if mails[i] == "T138":
mail = "email_1#gmail.com"
elif mails[i] == "T139":
mail = "email_2#gmail.com"
"""
subject = 'My subject'
body = f"There you go" \n Name: {customer} \n WR: {wr} \n Phone: {phone} \n Street: {street} \n City: {city} \n Mobile: {mobile} \n Mobile2: {mobile2}"
email_text = """\
From: %s
To: %s
Subject: %s
%s
""" % (sent_from, ", ".join(mail), subject, body)
try:
server.sendmail(sent_from, [mail], email_text)
server.close()
print('Your email was sent!')
except:
print("Some error")
The program raises a KeyError: 0 after it enters the for loop, on the first line inside the loop: customer = customers[i]
I know that the commented part (the nested for loop) will raise the same error.
I'm banging my head on the wall, I think i've read and tried everything.
Where's my error?

Things start to go wrong here: list = my_tailed_df. In Python list() is a Built-in Type.
However, with list = my_tailed_df, you are overwriting the type. You can check this:
# before the line:
print(type(list))
<class 'type'>
list = my_tailed_df
# after the line:
print(type(list))
<class 'pandas.core.frame.DataFrame'> # assuming that your df is an actual df!
This is bad practice and adds no functional gain at the expense of confusion. E.g. customers = list['CUSTOMER'] is doing the exact same thing as would customers = my_tailed_df['CUSTOMER'], namely creating a pd.Series with the index from my_tailed_df. So, first thing to do, is to get rid of list = my_tailed_df and to change all those list[...] snippets into my_tailed_df[...].
Next, let's look at your error. for i in range(len(mails)): generates i = 0, 1, ..., len(mails)-1. Hence, what you are trying to do, is access the pd.Series at the index 0, 1 etc. If you get the error KeyError: 0, this must simply mean that the index of your original df does not contain this key in the index (e.g. it's a list of IDs or something).
If you don't need the original index (as seems to be the case), you could remedy the situation by resetting the index:
my_tailed_df.reset_index(drop=True, inplace=True)
print(my_tailed_df.index)
# will get you: RangeIndex(start=0, stop=x, step=1)
# where x = len(my_tailed_df)-1 (== len(mails)-1)
Implement the reset before the line customers = my_tailed_df['CUSTOMER'] (so, instead of list = my_tailed_df), and you should be good to go.
Alternatively, you could keep the original index and change for i in range(len(mails)): into for i in mails.index:.
Finally, you could also do for idx, element in enumerate(mails.index): if you want to keep track both of the position of the index element (idx) and its value (element).

Related

Unable to loop correctly

I am working on assignment to extract emails from the mailbox.
Below are my codes, I am referencing from this case and combine with some other research online:
import win32com.client
import pandas as pd
import os
outlook = win32com.client.Dispatch("Outlook.Aplication").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6).Folders["Testmails"]
condition = pd.read_excel(r"C:\Users\Asus\Desktop\Python\Condition.xlsx", sheet_name = 'endword')
emails = condition.iloc[:,1].tolist()
done = outlook.GetDefaultFolder(6).Folders["Testmails"].Folders["Done"]
Item = inbox.Items.GetFirst()
add = Item.SenderEmailAddress
for attachment in Item.Attachments:
if any([add.endswith(m) for m in condition]) and Item.Attachments.Count > 0:
print(attachment.FileName)
dir = "C:\\Users\\Asus\\Desktop\\Python\\Output\\"
fname = attachment.FileName
outpath = os.path.join(dir, fname)
attachment.SaveAsFile(outpath)
Item.Move(done)
The code above is running, but it only saves the first email attachment, and the other email that matches the condition is not saving.
The condition file is like below, if is gmail to save in file A. But I am not sure if we can do by vlookup in loops.
mail end Directory
0 gmail.com "C:\\Users\\Asus\\Desktop\\Output\\A\\"
1 outlook.com "C:\\Users\\Asus\\Desktop\\Output\\A\\"
2 microsoft.com "C:\\Users\\Asus\\Desktop\\Output\\B\\"
Thanks for all the gurus who is helping much. I have edited the codes above but now is facing other issues on looping.
Fix Application on Dispatch("Outlook.Aplication") should be double p
On filter add single quotation mark round 'emails'
Example
Filter = "[SenderEmailAddress] = 'emails'"
for loop, you are using i but then you have print(attachment.FileName) / attachment.SaveAsFile
use i for all - print(i.FileName) / i.SaveAsFile or attachment
import win32com.client
Outlook = win32com.client.Dispatch("Outlook.Application")
olNs = Outlook.GetNamespace("MAPI")
Inbox = olNs.GetDefaultFolder(6)
Filter = "[SenderEmailAddress] = '0m3r#email.com'"
Items = Inbox.Items.Restrict(Filter)
Item = Items.GetFirst()
if Item.Attachments.Count > 0:
for attachment in Item.Attachments:
print(Item.Attachments.Count)
print(attachment.FileName)
attachment.SaveAsFile(r"C:\path\to\my\folder\Attachment.xlsx")
The 'NoneType' object has no attribute 'Attachments' error means that you're trying to get attachments from something that is None.
You're getting attachments in only one place:
for i in Item.Attachments:
...
so we can conclude that the Item here is None.
By looking at Microsoft's documentation we can see that the method...
Returns Nothing if no first object exists, for example, if there are no objects in the collection
Therefore, I'd imagine there's an empty collection, or no emails matching your filter
To handle this you could use an if statement
if Item is not None:
for i in Item.Attachments:
...
else:
pass # Do something here if there's nothing matching your filter

How to make a time restriction in outlook using python?

I am making a program that:
opens outlook
find emails per subject
extract some date from emails (code and number)
fills these data in excel file in.
Standard email looks like this:
Subject: Test1
Hi,
You got a new answer from user Alex.
Code: alex123fj
Number1: 0611111111
Number2: 1020
Number3: 3032
I encounter 2 main problems in the process.
Firstly, I do not get how to make time restriction for emails in outlook. For example, if I want to read emails only from yesterday.
Secondly, all codes and numbers from email I save in lists. But every item gets this ["alex123fj/r"] in place from this ["alex123fj"]
I would appreciate any help or advice, that is my first ever program in Python.
Here is my code:
import win32com.client
import re
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.Folders('myemail#....').Folders('Inbox')
messages = inbox.Items
def get_code(messages):
codes_lijst = []
for message in messages:
subject = message.subject
if subject == "Test1":
body = message.body
matches = re.finditer("Code:\s(.*)$", body, re.MULTILINE)
for match in matches:
codes_lijst.append(match.group(1))
return codes_lijst
def get_number(messages):
numbers_lijst = []
for message in messages:
subject = message.subject
if subject == "Test1":
body = message.body
matches = re.finditer("Number:\s(.*)$", body, re.MULTILINE)
for match in matches:
numbers_lijst.append(match.group(1))
return numbers_lijst
code = get_code(messages)
number = get_number(messages)
print(code)
print(number)
Firstly, never loop through all items in a folder. Use Items.Find/FindNext or Items.Restrict with a restriction on ConversationTopic (e.g. [ConversationTopic] = 'Test1').
To create a date/time restriction, add a range restriction ([ReceivedTime] > 'some value') and [ReceivedTime] < 'other value'

Automating sending bulk emails from an excel file

Email Notification List
I want to automatically send custom/personalized emails to school parents on my list as seen in the image above, giving details of the students' course, and other relevant info.To do that, I am using openpyxl and smtplib modules.
However, my looping through the rows and items in my dictionaries bearing student information and parent information isn't working well. Every time I run the script, I get this type of error
Traceback (most recent call last):
File "class_email_notification.py", line 42, in <module>
for course, account, link, clasS_day, session in class_info.items():
ValueError: not enough values to unpack (expected 5, got 2)
Both my xlsx and .py files are located in the same directory. Snippet of .py file is this:
import openpyxl, smtplib, sys, datetime
from time import sleep
wb = openpyxl.load_workbook('course_notification.xlsx')
sheet = wb['student info']
parents = {}
class_info = {}
students = {}
day = datetime.datetime(2020, 6, 1, 8, 0 ,0).strftime('%A, %B, %d, %Y %I:%M:%S %p')
for r in range(2, sheet.max_row + 1):
parent_name = sheet.cell(row = r, column = 8).value
parent_email = sheet.cell(row = r, column = 9).value
parents[parent_name] = parent_email
course = sheet.cell(row = r, column = 3).value
account = sheet.cell(row = r, column = 4).value
link = sheet.cell(row = r, column = 5).value
clasS_day = sheet.cell(row = r, column = 6).value
session = sheet.cell(row = r, column = 7).value
class_info[course] = account, link, clasS_day, session
gender = sheet.cell(row = r, column = 2).value
student_name = sheet.cell(row = r, column = 1).value
smtpObj = smtplib.SMTP('smtp.gmail.com')
smtpObj.ehlo()
smtpObj.starttls()
for parent_name, parent_email in parents.items():
for course, account, link, clasS_day, session in class_info.items():
smtpObj.login('example#gmail.com', sys.argv[1])
if gender.lower() == 'male':
gender = 'his'
body = 'Subject: Tinker Education Online Classes\n\nDear %s,\n\nHope you and your family are doing well and keeping safe while at home. Tinker Education would like to bring to your attention that our online classes will commence on ', day,' .\n\nOur teachers would like to have an insanely great start of the term with our students. Hence, you are requested to notify your child of ',gender, 'class time. Class details are as follows:\n\n%s\n\nDay: %s\nSession: %s\nCLP:\n%s\nVideo link: %s\n\n%s is requested to keep time of ', gender, ' %s %s\n\nKind regards,\nTinker Desk'%(parent_name, course, clasS_day, session, account, link, student_name, course, session)
else:
gender = 'her'
body = 'Subject: Tinker Education Online Classes\n\nDear %s,\n\nHope you and your family are doing well and keeping safe while at home. Tinker Education would like to bring to your attention that our online classes will commence on ', day,' .\n\nOur teachers would like to have an insanely great start of the term with our students. Hence, you are requested to notify your child of ',gender, 'class time. Class details are as follows:\n\n%s\n\nDay: %s\nSession: %s\nCLP:\n%s\nVideo link: %s\n\n%s is requested to keep time of ', gender, ' %s %s\n\nKind regards,\nTinker Desk'%(parent_name, course, clasS_day, account, session, link, student_name, course, session)
print('Sending emails to %s through %s'%(parent_name, parent_email))
send_email_status = smtpObj.sendmail('my_email#gmail.com', parent_email, body)
if send_email_status != {}:
print('There was an error sending an email to %s through %s'(parent_name, parent_email))
smtpObj.quit()
My for loop while iterating through the keys and values in the dictionaries do not provide enough values to unpack. What am I missing?
I have tried to understand why all items in the dicts cannot be fetched. My assumption is that my logic is not so correct.
I have made reference to Automate the boring stuff: sending emails, and I can understand how to go about it. But really knowing the exact logic as to why the dicts on my script do not entirely unpack is my issue.
The structure returned from iterating through class_info.items() is a nested tuple here, where the first element is the key and the second element is the associated value. In this case, it's (account, link, clasS_day, session).
You could get this to work in its current form by using:
for course, (account, link, clasS_day, session) in class_info.items():
i.e. replicating the tuple structure returned by class_info.items()

How can I search the Outlook (2010) Global Address List for a name?

I have a list of names, some of them complete, some truncated. I would like to search the Outlook address list for matches for these names.
The closest I have come is this Python code which came from ActiveState Code, but it does not search the global addresses, only my (local?) list, which has 3 addresses in it, which is obviously not right. There should be thousands of records.
Any hints welcome. I have googled and read dozens of pages, but nothing conclusive. I'd rather not connect to the LDAP directly, I think that is a policy violation in my org and besides I am not sure it's possible. Would like to do this via the Outlook API if possible.
DEBUG=1
class MSOutlook:
def __init__(self):
self.outlookFound = 0
try:
self.oOutlookApp = \
win32com.client.gencache.EnsureDispatch("Outlook.Application")
self.outlookFound = 1
except:
print("MSOutlook: unable to load Outlook")
self.records = []
def loadContacts(self, keys=None):
if not self.outlookFound:
return
# this should use more try/except blocks or nested blocks
onMAPI = self.oOutlookApp.GetNamespace("MAPI")
ofContacts = \
onMAPI.GetDefaultFolder(win32com.client.constants.olFolderContacts)
if DEBUG:
print("number of contacts:", len(ofContacts.Items))
for oc in range(len(ofContacts.Items)):
contact = ofContacts.Items.Item(oc + 1)
if contact.Class == win32com.client.constants.olContact:
if keys is None:
# if we were't give a set of keys to use
# then build up a list of keys that we will be
# able to process
# I didn't include fields of type time, though
# those could probably be interpreted
keys = []
for key in contact._prop_map_get_:
if isinstance(getattr(contact, key), (int, str, unicode)):
keys.append(key)
if DEBUG:
keys.sort()
print("Fields\n======================================")
for key in keys:
print(key)
record = {}
for key in keys:
record[key] = getattr(contact, key)
if DEBUG:
print(oc, record['FullName'])
self.records.append(record)
Random links:
MSDN - How to create a Global Address List programmatically using Visual Basic
Infinitec - How to get the Global Address List programatically
Return Boolean True - Downloading the Global Address List from Outlook/Exchange
Grokbase - [python-win32] getting global addressbook with extended mapi
Stack Overflow - Searching Outlook Global Address
Another failed attempt at List
ActiveStater Code - RE: Email Address Lookup
Stack Overflow - Retrieving outlook Contacts via python
This is where I got the link above
Stack Overflow - Fetching Outlook Contacts with Python
Doesn't run at all
Python for Windows - Automating Microsoft Outlook
Just default address book. Besides I want to search, not list all.
If anyone can come up with a solution I don't mind if it's C++, VB, Perl, Python, etc.
Problem solved!
Thanks to Dmitry's answers I could produce this minimal Python code which demonstrates what I wanted to achieve:
from __future__ import print_function
import win32com.client
search_string = 'Doe John'
outlook = win32com.client.gencache.EnsureDispatch('Outlook.Application')
recipient = outlook.Session.CreateRecipient(search_string)
recipient.Resolve()
print('Resolved OK: ', recipient.Resolved)
print('Is it a sendable? (address): ', recipient.Sendable)
print('Name: ', recipient.Name)
ae = recipient.AddressEntry
email_address = None
if 'EX' == ae.Type:
eu = ae.GetExchangeUser()
email_address = eu.PrimarySmtpAddress
if 'SMTP' == ae.Type:
email_address = ae.Address
print('Email address: ', email_address)
Your code above deals with the contacts in the default Contacts folder. If you want to check if a given name is in Outlook (either as a contact or in GAL), simply call Application.Session.CreateRecipient followed by Recipient.Resolve. If the call returns true, you can read the Recipient.Address and various other properties.
The method in #Prof.Falken's solution doesn't always work when there are multiple matches for the search string. I found another solution, which is more robust as it uses exact match of the displayname.
It's inspired by How to fetch exact match of addressEntry object from GAL (Global Address List).
import win32com.client
search_string = 'Doe John'
outlook = win32com.client.gencache.EnsureDispatch('Outlook.Application')
gal = outlook.Session.GetGlobalAddressList()
entries = gal.AddressEntries
ae = entries[search_string]
email_address = None
if 'EX' == ae.Type:
eu = ae.GetExchangeUser()
email_address = eu.PrimarySmtpAddress
if 'SMTP' == ae.Type:
email_address = ae.Address
print('Email address: ', email_address)

How can I print organized ngrams from my email?

I need to do two things at this point but I need your help:
A best practice to clean up data - programmatically deleting superfluous tags & the '>>>>>>>', plus other non meaningful communication flotsam and jetsum
Once it's cleaned - how do I pack it up to work nice in django & sqlite.
Do I make it into a csv based on date, person, subject, words then input them into my data classes within my database?
Well, before I get into the database, I'd like to be able to sort a sort and display the data cleanly - I have very little experience putting things into databases, the closest I do is work from XML, csv and JSON.
I need to have the ngrams by rankings, for example how many times a certain word shows up in a series of emails by a person. I'm trying to get closer to knowing the streams of how people are talking to me about subjects, etc. a very elementary version of Jon Kleinberg's work analyzing his own emails.
be gentle, be rough but please be helpful :)!
> My output currently looks like this: : 1, 'each': 1, 'Me': 1, 'IN!\r\n\r\n2012/1/31': 1, 'calculator.\r\n>>>>>>\r\n>>>>>>': 1, 'people': 1, '=97MB\r\n>\r\n>': 1, 'we': 2, 'wrote:\r\n>>>>>>\r\n>>>>>>': 1, '=\r\nwrote:\r\n>>>>>\r\n>>>>>>': 1, '2012/1/31': 2, 'are': 1, '31,': 5, '=97MB\r\n>>>>\r\n>>>>': 1, '1:45': 1, 'be\r\n>>>>>': 1, 'Sent':
import getpass, imaplib, email
# NGramCounter builds a dictionary relating ngrams (as tuples) to the number
# of times that ngram occurs in a text (as integers)
class NGramCounter(object):
# parameter n is the 'order' (length) of the desired n-gram
def __init__(self, text):
self.text = text
self.ngrams = dict()
# feed method calls tokenize to break the given string up into units
def tokenize(self):
return self.text.split(" ")
# feed method takes text, tokenizes it, and visits every group of n tokens
# in turn, adding the group to self.ngrams or incrementing count in same
def parse(self):
tokens = self.tokenize()
#Moves through every individual word in the text, increments counter if already found
#else sets count to 1
for word in tokens:
if word in self.ngrams:
self.ngrams[word] += 1
else:
self.ngrams[word] = 1
def get_ngrams(self):
return self.ngrams
#loading profile for login
M = imaplib.IMAP4_SSL('imap.gmail.com')
M.login("EMAIL", "PASS")
M.select()
new = open('liamartinez.txt', 'w')
typ, data = M.search(None, 'FROM', 'SEARCHGOES_HERE') #Gets ALL messages
def get_first_text_part(msg): #where should this be nested?
maintype = msg.get_content_maintype()
if maintype == 'multipart':
for part in msg.get_payload():
if part.get_content_maintype() == 'text':
return part.get_payload()
elif maintype == 'text':
return msg.get_payload()
for num in data[0].split(): #Loops through all messages
typ, data = M.fetch(num, '(RFC822)') #Pulls Message
msg = email.message_from_string(data[0][2]) #Puts message into easy to use python objects
_from = msg['from'] #pull from
_to = msg['to'] #pull to
_subject = msg['subject'] #pull subject
_body = get_first_text_part(msg) #pull body
if _body:
ngrams = NGramCounter(_body)
ngrams.parse()
_feed = ngrams.get_ngrams()
# print "\n".join("\t".join(str(_feed) for col in row) for row in tab)
print _feed
# print 'Content-Type:',msg.get_content_type()
# print _from
# print _to
# print _subject
# print _body
#
new.write(_from)
print '---------------------------------'
M.close()
M.logout()
There is nothing wrong in your main loop. The process though is somewhat slow as you need to retrieve all your emails from an external server. What I'd suggest is to download all the messages on the client once. Then save them into a database (sqlite, zodb, mongodb.. the one you prefer) and then perform all the analysis that you want on the db objects afterwards. The two processes (downloading and analyzing) are better kept a part one from each other otherwise tuning them up would result complicated and code complexity would increase.
replace
if _body:
ngrams = NGramCounter(_body)
ngrams.parse()
_feed = ngrams.get_ngrams()
# print "\n".join("\t".join(str(_feed) for col in row) for row in tab)
print _feed
with
if _body:
ngrams = NGramCounter(" ".join(_body.strip(">").split()))
ngrams.parse()
_feed = ngrams.get_ngrams()
print _feed

Categories

Resources