Looping through to create a tuple - python

I'm attempting to implement the mass mail send out.
Here is the mass mail doc: Just a link to the Django Docs
In order to achieve this I need create this tuple:
datatuple = (
('Subject', 'Message.', 'from#example.com', ['john#example.com']),
('Subject', 'Message.', 'from#example.com', ['jane#example.com']),
)
I query the ORM for some recipients details. Then I would imagine there's some looping involved, each time adding another recipient to the tuple. All elements of the message are the same except for username and email.
So far I have:
recipients = notification.objects.all().values_list('username','email')
# this returns [(u'John', u'john#example.com'), (u'Jane', u'jane#example.com')]
for recipient in recipients:
to = recipient[1] #access the email
subject = "my big tuple loop"
dear = recipient[0] #access the name
message = "This concerns tuples!"
#### add each recipient to datatuple
send_mass_mail(datatuple)
I've been trying something along the lines of this :
SO- tuple from a string and a list of strings

If I understand correctly, this is pretty simple with a comprehension.
emails = [
(u'Subject', u'Message.', u'from#example.com', [address])
for name, address in recipients
]
send_mass_mail(emails)
Note that we leverage Python's ability to unpack tuples into a set of named variables. For each element of recipients, we assign its zeroth element to name and its first element to address. So in the first iteration, name is u'John' and address is u'john#example.com'.
If you need to vary the 'Message.' based on the name, you can use string formatting or any other formatting/templating mechanism of your choice to generate the message:
emails = [
(u'Subject', u'Dear {}, Message.'.format(name), u'from#example.com', [address])
for name, address in recipients
]
Since the above are list comprehensions, they result in emails being a list. If you really need this to be a tuple instead of a list, that's easy, too:
emails = tuple(
(u'Subject', u'Message.', u'from#example.com', [address])
for name, address in recipients
)
For this one, we're actually passing a generator object into the tuple constructor. This has the performance benefits of using a generator without the overhead of creating an intermediate list. You can do that pretty much anywhere in Python where an iterable argument is accepted.

Just a little bit of cleanup needed here:
1) actually build the tuple in the loop (this is a bit tricky since you need the extra comma to ensure that a tuple is appended and not the values from the tuple)
2) move the send_mass_mail call outside the loop
This should be working code:
recipients = notification.objects.all().values_list('username','email')
# this returns [(u'John', u'john#example.com'), (u'Jane', u'jane#example.com')]
datatuple = []
for recipient in recipients:
to = recipient[1] #access the email
subject = "my big tuple loop"
dear = recipient[0] #access the name
message = "This concerns tuples!"
#### add each recipient to datatuple
datatuple.append((subject, message, "from#example.com", [to,]),)
send_mass_mail(tuple(datatuple))
EDIT:
jpmc26's technique is definitely more efficient, and if you're planning to have a large email list to send to you should use that. Most likely you should use whichever code makes the most sense to you personally so that when your requirements change you can easily understand how to update.

Related

How to check number of attachments in email?

I'm working with exchangelib and python3 to manage an Exchange mailbox, so far my code works ok, but I wanted to check how many attachments an email has before to go forward.
This is what I have so far:
def send_email(account, subject, body, recipients, attachments=None):
to_recipients = []
for recipient in recipients:
to_recipients.append(Mailbox(email_address=recipient))
# Create message
m = Message(account=account,
folder=account.sent,
subject=subject,
body=body,
to_recipients=to_recipients)
b = attachments.count
log.info(b)
m.attach(attachments)
m.send_and_save()
I'm calling this function to create a new email from a previous one I've received with attachments. When the email has a just one attachment it works fine, however when the received email has more than one attachment it fail. That is why I wanted to check how many attachments the received email has before to proceed.
I found out this attribute for attachments object but the result I got is this:
<built-in method count of list object at 0x10a23be10>
So, how could I check if the attachments object, which is a type FileAttachment,has more than one attachment? Even better, how could I attach more than one attachment to my new email?
For the last question I have this code, which does not work:
for attachment_name, attachment_content in attachments or []:
log.info('loop attachments')
file = FileAttachment(name=attachment_name, content=attachment_content)
m.attach(file)
This is the error I'm receiving:
for attachment_name, attachment_content in attachments or []:
TypeError: cannot unpack non-iterable FileAttachment object
There are multiple misconceptions here.
First, you need to check first which type your attachments argument is. Judging by the output, you are passing a list (the built-in list type). Later on, you are printing attachments.count which is actually the list.count() method (see https://docs.python.org/3.8/tutorial/datastructures.html), which does not make sense to print. If you want to get the size of the attachments argument, use len(attachments), since it's just a list.
A single FileAttachment is one attachment, not a list of attachments. Instead, I assume that your attatchments argument is a list of FileAttachment objects.
Regarding the last TypeError, you are iterating over a list of FileAttachment objects, but then treating each object as if it were a tuple by trying to unpack the object. That won't work. If you want to access the name and content attributes of each FileAttachment, do this instead:
for a in attachments:
print(a.name, a.content)

How do I properly format this API call?

I am making a telegram chatbot and can't figure out how to take out the [{' from the output.
def tether(bot, update):
tetherCall = "https://api.omniexplorer.info/v1/property/31"
tetherCallJson = requests.get(tetherCall).json()
tetherOut = tetherCallJson ['issuances'][:1]
update.message.reply_text("Last printed tether: " + str (tetherOut)+" Please take TXID and past it in this block explorer to see more info: https://www.omniexplorer.info/search")
My user will see this as a response: [{'grant': '25000000.00000000', 'txid': 'f307bdf50d90c92278265cd92819c787070d6652ae3c8af46fa6a96278589b03'}]
This looks like a list with a single dict in it:
[{'grant': '25000000.00000000',
'txid': 'f307bdf50d90c92278265cd92819c787070d6652ae3c8af46fa6a96278589b03'}]
You should be able to access the dict by indexing the list with [0]…
tetherOut[0]
# {'grant': '25000000.00000000',
# 'txid': 'f307bdf50d90c92278265cd92819c787070d6652ae3c8af46fa6a96278589b03'}
…and if you want to get a particular value from the dict you can index by its name, e.g.
tetherOut[0]['txid']
# 'f307bdf50d90c92278265cd92819c787070d6652ae3c8af46fa6a96278589b03'
Be careful chaining these things, though. If tetherOut is an empty list, tetherOut[0] will generate an IndexError. You'll probably want to catch that (and the KeyError that an invalid dict key will generate).

Search for a list of emails with imaplib python

I am facing a following problem, I want to have an allowed senders list in my email parser so it will look like:
allowed_emails = ["email1#gmail.com", "email2#gmail.com", "email3#gmail.com"]
I want to use this list in this line, for now it works only with one allowed_sender, how to send a list as an argument?
result, data = mail.search(None,'(UNSEEN FROM "%s")' % allowed_sender)

How to obtain the recipient list from email using IMAPClient in Python

I am using the IMAPClient library in Python. I am able to download the attached document in the email. I am interested in only Excel files.
I am interested to extract the recipient list from the email. Any idea how to do it in Python ?
Here is the code snippet which might be useful
for ind_mail in emails:
msg_string = ind_mail['RFC822'].decode("utf-8")
#print(msg_string.decode("utf-8"))
email_msg = email.message_from_string(msg_string)
for part in email_msg.walk():
# Download only Excel File
filetype = part.get_content_type()
if(filetype == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'):
#download
The straightforward answer to your question is to get the corresponding headers' values, i.e.:
to_rcpt = email_msg.get_all('to', [])
cc_rcpt = email_msg.get_all('cc', [])
, inside that first loop. The MIME standard doesn't enforce uniqueness on the headers (though strongly suggests it), thus get_all; if not present, you'll still have an empty list for a consecutive loop.
But as tripleee has rightfully pointed out, the mime headers can be easily censored, spoofed or simply removed.
Yet this is the only info persisted and returned by a server, and all mail clients use to present to us :)
Calling msg.get_all will return a list containing one entry per one header, so if you have multiple header, you'll get a list per header
BUT
If one header has multiple emails in a coma-separated way, you will only get one string and you'll have to split it.
The best way to have the list of all the emails from a specific header is to use getaddresses (https://docs.python.org/3/library/email.utils.html#email.utils.getaddresses)
from email.utils import getaddresses
to_rcpt = getaddresses(email_msg.get_all('to', []))
get_all will return an array of all the "To:" headers, and getaddresses will parse each entry and return as many emails as present on each headers. For instance:
message = """
To: "Bob" <email1#gmail.com>, "John" <email2#gmail.com>
To: email3#gmail.com, email4#gmail.com
"""
to_rcpt = getaddresses(email_msg.get_all('to', []))
=> [('Bob', 'email1#gmail.com'), ('John', 'email2#gmail.com'), ('', 'email3#gmail.com'), ('', 'email4#gmail.com')]

Exclude specific email address with regex

I have this regex for extracting emails which works fine:
([a-zA-Z][\w\.-]*[a-zA-Z0-9])#([a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z])
however there are some e-mails I don't want to include like:
server#example.com
noreply#example.com
name#example.com
I've been trying to add things like ^(?!server|noreplay|name) but isn't no working.
Also by using parentheses as above will afect tuples with (name, domain) ?
Just check for those email addresses after you extract them...
bad_addresses=['server#example.com', 'noreply#example.com', 'name#example.com']
emails=re.findall('[a-zA-Z][\w\.-]*[a-zA-Z0-9])#([a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]', contentwithemails)
for item in emails[:]:
if item in bad_addresses:
emails.remove(item)
You have to do a slice of emails ( emails[:] ), because you can't do a for loop on a list that keeps changing size. This creates a "ghost" list that can be read while the real list is acted on.
Check the results from your regex for any emails that match the bad emails list.
results = list_from_your_regex
invalids = ['info', 'server', 'noreply', ...]
valid_emails = [good for good in results if good.split('#')[0] not in invalids]

Categories

Resources