I have method that sends an email and I want to make parametrize pytest test, but when I run my test it doesn't run and in logs it shows no tests ran. What's wrong with my test?
pytest scenario:
import pytest
import new_version
#pytest.mark.parametrize("email", ['my_email#gmail.com'])
def send_email(email):
new_version.send_mail(email)
method that sends email:
# Imports
import smtplib
import time
# Language
error_title = "ERROR"
send_error = "I'm waiting {idle} and I will try again."
send_error_mail = "Mail invalid"
send_error_char = "Invalid character"
send_error_connection_1_2 = "Connection problem..."
send_error_connection_2_2 = "Gmail server is down or internet connection is instabil."
login_browser = "Please log in via your web browser and then try again."
login_browser_info = "That browser and this software shood have same IP connection first time."
# Gmaild ID fro login
fromMail = "myemail#gmail.com"
fromPass = "pass"
# Some configurations
mailDelay = 15
exceptionDelay = 180
# SEND MAILS
def send_mail(email, thisSubject="Just a subject",
thisMessage="This is just a simple message..."):
# To ho to send mails
mailTo = [
email
]
# If still have mails to send
while len(mailTo) != 0:
sendItTo = mailTo[0] # Memorise what mail will be send it (debug purpose)
try:
# Connect to the server
server = smtplib.SMTP("smtp.gmail.com:587")
server.ehlo()
server.starttls()
# Sign In
server.login(fromMail, fromPass)
# Set the message
message = f"Subject: {thisSubject}\n{thisMessage}"
# Send one mail
server.sendmail(fromMail, mailTo.pop(0), message)
# Sign Out
server.quit()
# If is a problem
except Exception as e:
# Convert error in a string for som checks
e = str(e)
# Show me if...
if "The recipient address" in e and "is not a valid" in e:
print(f"\n>>> {send_error_mail} [//> {sendItTo}\n")
elif "'ascii'" in e and "code can't encode characters" in e:
print(f"\n>>> {send_error_char} [//> {sendItTo}\n")
elif "Please" in e and "log in via your web browser" in e:
print(f"\n>>> {login_browser}\n>>> - {login_browser_info}")
break
elif "[WinError 10060]" in e:
if "{idle}" in send_error:
se = send_error.split("{idle}");
seMsg = f"{se[0]}{exceptionDelay} sec.{se[1]}"
else:
seMsg = send_error
print(f"\n>>> {send_error_connection_1_2}\n>>> {send_error_connection_2_2}")
print(f">>> {seMsg}\n")
# Wait 5 minutes
waitTime = exceptionDelay - mailDelay
if waitTime <= 0:
waitTime = exceptionDelay
time.sleep(waitTime)
else:
if "{idle}" in send_error:
se = send_error.split("{idle}");
seMsg = f"{se[0]}{exceptionDelay} sec.{se[1]}"
else:
seMsg = send_error
print(f">>> {error_title} <<<", e)
print(f">>> {seMsg}\n")
# Wait 5 minutes
time.sleep(exceptionDelay)
# If are still mails wait before to send another one
if len(mailTo) != 0:
time.sleep(mailDelay)
logs:
platform darwin -- Python 3.7.5, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /Users/nikolai/Documents/Python/gmail/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/nikolai/Documents/Python/gmail
collected 0 items
Unless you change the discovery prefixes for Pytest, test functions must be named test_*:
import pytest
import new_version
#pytest.mark.parametrize("email", ['my_email#gmail.com'])
def test_send_email(email):
new_version.send_mail(email)
Related
I am attempting to port some old java code to python.
I am using pymqi to connect to a queue manager and query for all messageflow statistics topics using the topic string: $SYS/Broker/+/StatisticsAccounting/Archive/#
When using the existing java program messages are read from the topic without issue.
When using the new python code it is able to connect and query the topic without issue but always gives the message
Reason 2033: FAILED: MQRC_NO_MSG_AVAILABLE
Stats messages are published by the broker for each messageflow every 10 minutes, and I have left the new code running for over 30minutes, never having received a message.
I've also tried setting
get_opts['WaitInterval'] = pymqi.CMQC.MQWI_UNLIMITED
and sitting around for 20minutes rather than using a loop, but no luck.
Is there any IIB server config that might be impacting the messages that I am able to see, or are there other options I should be using within the client?
import pymqi
queue_manager = 'MYQM'
channel = 'MYAPP.SVRCONN'
host = 'MYHOST'
port = 'MYPORT'
topic_string = '$SYS/Broker/+/StatisticsAccounting/Archive/#'
conn_info = '%s(%s)' % (host, port)
user = ""
password = ""
qmgr = pymqi.QueueManager(None)
qmgr.connect_tcp_client(queue_manager, pymqi.CD(), channel, conn_info, user, password)
sub_desc = pymqi.SD()
sub_desc['Options'] = pymqi.CMQC.MQSO_CREATE + pymqi.CMQC.MQSO_RESUME + pymqi.CMQC.MQSO_MANAGED
sub_desc.set_vs('SubName', 'apptest')
sub_desc.set_vs('ObjectString', topic_string)
sub = pymqi.Subscription(qmgr)
sub.sub(sub_desc=sub_desc)
get_opts = pymqi.GMO(Options=pymqi.CMQC.MQGMO_WAIT)
get_opts['WaitInterval'] = 10000
md = pymqi.md()
keep_running = True
while keep_running:
try:
# Reset the MsgId, CorrelId & GroupId so that we can reuse
# the same 'md' object again.
md.MsgId = pymqi.CMQC.MQMI_NONE
md.CorrelId = pymqi.CMQC.MQCI_NONE
md.GroupId = pymqi.CMQC.MQGI_NONE
message = sub.get(None, md, get_opts)
print('Have message from Queue')
print(message)
except pymqi.MQMIError as e:
if e.comp == pymqi.CMQC.MQCC_FAILED and e.reason == pymqi.CMQC.MQRC_NO_MSG_AVAILABLE:
print("no message?")
print(e)
pass
else:
# Some other error condition.
raise
except (UnicodeDecodeError, ValueError) as e:
print('Message is not valid json')
print(e)
print(message)
continue
except KeyboardInterrupt:
print('Have received a keyboard interrupt')
keep_running = False
sub.close(sub_close_options=0,close_sub_queue=True)
qmgr.disconnect()
I need to send a message when my program is done running, but I also want to include variables such as how long the program took to run in the text.
Here is my code for the texting:
import smtplib
carriers = {
'att': '#mms.att.net',
'tmobile': ' #tmomail.net',
'verizon': '#vtext.com',
'sprint': '#page.nextel.com'
}
def send(message):
# Replace the number with your own, or consider using an argument\dict for multiple people.
to_number = 'xxxxxxxxxx{}'.format(carriers['verizon'])
auth = ('xxxxx', 'xxxx')
# Establish a secure session with gmail's outgoing SMTP server using your gmail account
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(auth[0], auth[1])
# Send text message through SMS gateway of destination number
server.sendmail(auth[0], to_number, message)
Obviously, I replaced my info with the xxx.
Now, to send my text I'm calling the function using:
found = 'The program is done!'
timetook = "Time took: %s (HOUR:MIN:SEC)" % timedelta(seconds=round(elapsed_time_secs))
send(found)
send(timetook)
It just sends blank texts for the timetook, but the program is done message works fine. How do I send the timetook?
The problem is that you aren't following the rules for SMTP. Below is the equivalent solution I wrote for my own use many years ago.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""SMS (short message service) functions."""
import logging
import smtplib
import sys
import rate_limiting
SMTP_GATEWAY = 'localhost'
FROM_ADDR = 'me#domain.us'
PHONE_NUMBER = '4081234567'
# T-Mobile SMS email gateway
# TO_ADDR = PHONE_NUMBER + '#tmomail.net'
# Verizon SMS email gateway
TO_ADDR = PHONE_NUMBER + '#vtext.com'
# Allow only three SMS messages per minute and five per hour.
short_term_rate_limiter = rate_limiting.SimpleRateLimiter(3, 60)
long_term_rate_limiter = rate_limiting.SimpleRateLimiter(5, 60 * 60)
def SendSms(msg, why=''):
"""Send a SMS message."""
short_term_rate_exceeded = short_term_rate_limiter()
long_term_rate_exceeded = long_term_rate_limiter()
if short_term_rate_exceeded or long_term_rate_exceeded:
logging.warning('SMS message rate exceeded, dropping msg: %s', msg)
return
smtp_conn = smtplib.SMTP(SMTP_GATEWAY)
hdrs = 'From: {}\r\nTo: {}\r\n\r\n'.format(FROM_ADDR, TO_ADDR)
if why:
hdrs += 'Subject: {}\r\n'.format(why[:20])
hdrs += "\r\n\r\n"
max_msg_len = 140 - 3 - min(20, len(why))
msg = hdrs + msg[:max_msg_len]
# Make sure the message has only ASCII characters.
msg = msg.encode('ascii', errors='replace').decode('ascii')
smtp_conn.sendmail(FROM_ADDR, [TO_ADDR], msg)
smtp_conn.quit()
if __name__ == '__main__':
SendSms(' '.join(sys.argv[1:]))
How can i set one mailbox parser for user in few different threads ? Each user have 2 or more companies which mails he should parse. In my expample i'm always had errors.
class User():
services = {'company1': 'STOPED',
'company2': 'STOPED'}
def __init__(self, user_settings):
self.user_name = user_settings['User']
self.user_settings = user_settings
self.global_mailbox = None
def company1(self):
service_name = 'company1'
settings = self.user_settings['Company1']
gb_mail = False
def mailbox_check(mailbox):
nonlocal settings, service_name
mailbox.noop()
status, mails = mailbox.search(None, '(UNSEEN)', '(FROM "company1#cmp.com")')
....
if not settings['Mail Login']:
if self.global_mailbox:
mailbox = self.global_mailbox
gb_mail = True
else:
self.SET_GLOBAL_MAIL()
if not self.global_mailbox:
return
else:
mailbox = self.global_mailbox
else:
mail_host = 'imap.{0}'.format(settings['Mail Login'].split('#')[-1])
mail_login = settings['Mail Login']
mail_password = settings['Mail Password']
mailbox = imaplib.IMAP4_SSL(mail_host)
mailbox.sock.settimeout(43200)
mailbox.login(mail_login, mail_password)
mailbox.select('INBOX')
gb_mail = False
while self.services[service_name] != 'STOPED':
time.sleep(5)
new_orders = self.mailbox_check(mailbox)
if new_orders:
action()
if self.services[service_name] == 'STOPED':
break
def company2(self):
service_name = 'company2'
settings = self.user_settings['Company2']
gb_mail = False
def mailbox_check(mailbox):
nonlocal settings, service_name
mailbox.noop()
status, mails = mailbox.search(None, '(UNSEEN)', '(FROM "company2#cmp.com")')
....
if not settings['Mail Login']:
if self.global_mailbox:
mailbox = self.global_mailbox
gb_mail = True
else:
self.SET_GLOBAL_MAIL()
if not self.global_mailbox:
return
else:
mailbox = self.global_mailbox
else:
mail_host = 'imap.{0}'.format(settings['Mail Login'].split('#')[-1])
mail_login = settings['Mail Login']
mail_password = settings['Mail Password']
mailbox = imaplib.IMAP4_SSL(mail_host)
mailbox.sock.settimeout(43200)
mailbox.login(mail_login, mail_password)
mailbox.select('INBOX')
gb_mail = False
while self.services[service_name] != 'STOPED':
time.sleep(5)
new_orders = self.mailbox_check(mailbox)
if new_orders:
action()
if self.services[service_name] == 'STOPED':
break
def SET_GLOBAL_MAIL(self)
try:
gb_mail = self.user_settings['Global Mail']
mail_host = 'imap.{0}'.format(gb_mail['Login'].split('#')[-1])
mail_login = gb_mail['Login']
mail_password = gb_mail['Password']
mailbox = imaplib.IMAP4_SSL(mail_host)
mailbox.sock.settimeout(43200)
mailbox.login(mail_login, mail_password)
mailbox.select('INBOX')
self.global_mailbox = mailbox
except:
self.global_mailbox = None
pass
def START_ALL(self):
self.SET_GLOBAL_MAIL()
for service in self.services.keys():
self.services[service] = 'RUNNING'
threading.Thread(target=lambda: self.__getattribute__(service)(), name='{0} [{1} service thread]'.format(self.user_name, service), daemon=True).start()
>>>user = User(settings)
>>>user.START_ALL()
After few seconds i got these errors:
imaplib.IMAP4.abort: command: SEARCH => socket error: EOF
imaplib.IMAP4.abort: socket error: EOF
imaplib.IMAP4.abort: command: NOOP => socket error: EOF
imaplib.IMAP4.abort: socket error: [WinError 10014] The system detected an
invalid pointer address in attempting to use a pointer argument in a call
ssl.SSLError: [SSL: SSLV3_ALERT_BAD_RECORD_MAC] sslv3 alert bad record mac (_ssl.c:2273)
If for each thread i'm creating a new imap session all works fine, but GMAIL has a limit for simultaneous connection through imap. And user may have more than 15 companies for parsing. How to setup one global mail for user for all his actions ?
It does not make really sense to use the same IMAP connection for several conversations because there is one single socket connection and one single server side context. If one thread asks mailbox1 and a second asks for mailbox2, the server will select successively the 2 mailboxes and will stay in last one.
Not speaking of race conditions if two threads simultaneously read from same socket: each will only get quasi random partial data while the other part will be read by the other thread. I am sorry but this is not possible.
I have a list of emails i want to validate from same domain. Firstly, I check if the domain allows verfication or is a catchall (accepts all email user as valid). E.g Assuming [a#domain.com, b#domain.com] is the list, I will check if foo-fake#domain.com would be valid, if it is return from function.
If not use multiple thread to verify all emails in list - if all fail return the last item. If one is valid and other threads are running stop them and return the valid one.
from Queue import Queue
import threading
import smtplib
class MailWorker(threading.Thread):
kill = False
def __init__(self, in_que, out_que):
super(MailWorker, self).__init__()
self.in_que = in_que
self.out_que = out_que
def run(self):
while True:
email, host = self.in_que.get()
self.test_email(email, host)
self.in_que.task_done()
def test_email(self, email, host, retry=0):
status = "Unknown"
code = None
rand = "info#example.com"
try:
server = smtplib.SMTP(timeout=20)
server.connect(host, 25)
server.ehlo_or_helo_if_needed()
code, response = server.docmd('mail from:', "<{}>".format(rand))
code, response = server.docmd('rcpt to:', "<{}>".format(email))
if code == 421:
while retry < 3:
if retry >= 3:
server.quit()
self.out_que.put((email, "Service not available", code))
server.quit()
return self.test_email(email, host, retry=retry)
if code == 250:
status = 'valid'
self.out_que.put((email, status, code,))
except smtplib.SMTPServerDisconnected:
while retry < 3:
retry += 1
status = "(SMTP Disconnected Unexpectedly) Retry # {}".format(retry)
code = -2
time.sleep(2)
if retry >= 3:
self.out_que.put((email, "SMTP Disconnected", code))
else:
return self.test_email(email, host, retry=retry)
self.out_que.put((email, status, code,))
def check_email(emails, domain, index=0, is_main=False):
email = status = code = None
in_que = Queue(maxsize=10)
out_que = Queue(maxsize=10)
if 'brighthouse' in domain:
host = 'brighthouse-co-uk.mail.protection.outlook.com'
else:
host = 'eu-smtp-inbound-2.mimecast.com'
# is it a catchall? if it is i want to return from the function ---
# If the email is valid then it is not a catchall all so execute line 91
# till return checking multipe emails in threads but exit when one valid is found else do all email in list
if not is_main: # check if the email is main
in_que.put(('JUNK_EMAIL_CANT_BE_REAL_fewfwewefew#' + domain, host)) # put rubbish email in thread if valid it is a catchall, then exit
for i in range(1):
mw = MailWorker(in_que, out_que)
print mw
print "*" * 20
mw.daemon = True
mw.start()
print mw.getName()
print in_que.qsize(), " in queue size"
print out_que.qsize(), " out queue size"
print "*" * 20
email, status, code = out_que.get()
print "Contet = ", email, status, code
if code == 250:
print 'Domain is a Catch-All. email: %s host: %s' % (emails[0], host)
MailWorker.kill = True
return emails, "catchall", -99, index
elif code == -1:
return email, status, code, index
# in_que.join()
# out_que.join()
for index, email_address in enumerate(emails):
in_que.put((email_address, host,))
for i in range(10):
mw = MailWorker(in_que, out_que)
mw.daemon = True
mw.start()
while not out_que.empty():
email, status, code, index = out_que.get()
if code == 250:
MailWorker.kill = True
return email, status, code, index
in_que.join()
out_que.join()
return email, status, code, index
emails_list = [
['fred#brighthouse.co.uk', 'joe.fred#brighthouse.co.uk', 'joe#brighthouse.co.uk'],
['fred#cqs.com', 'joe.fred#cqs.com', 'joe#cqs.com']
]
for emails in emails_list:
domain = emails[0].split('#')[1]
print(check_email(emails, domain))
My expectation is for the next list item to run.
The result with the above code is:
# CODE FREEZES and DOESN'T RUN SECOND item IN EMAILS_LIST -- SEE output
"""
<MailWorker(Thread-1, initial)>
********************
Thread-1
1 in queue size
0 out queue size
********************
Contet = rubbish_fQ94hEAi93#brighthouse.co.uk valid 250
Domain is a Catch-All. email: fred#brighthouse.co.uk host: brighthouse-co-uk.mail.protection.outlook.com
(['fred#brighthouse.co.uk', 'joe.fred#brighthouse.co.uk', 'joe#brighthouse.co.uk'], 'catchall', -99, 0)
<MailWorker(Thread-2, initial)>
********************
Thread-2
0 in queue size
0 out queue size
******************** <---- code blocks here
"""
I wrote a python script to fetch all of my gmail. I have hundreds of thousands of old emails, of which about 10,000 were unread.
After successfully fetching all of my email, I find that gmail has marked all the fetched emails as "read". This is disastrous for me since I need to check all unread emails only.
How can I recover the information about which emails were unread? I dumped each mail object into files, the core of my code is shown below:
m = imaplib.IMAP4_SSL("imap.gmail.com")
m.login(user,pwd)
m.select("[Gmail]/All Mail")
resp, items = m.uid('search', None, 'ALL')
uids = items[0].split()
for uid in uids:
resp, data = m.uid('fetch', uid, "(RFC822)")
email_body = data[0][1]
mail = email.message_from_string(email_body)
dumbobj(uid, mail)
I am hoping there is either an option to undo this in gmail, or a member inside the stored mail objects reflecting the seen-state information.
For anyone looking to prevent this headache, consider this answer here. This does not work for me, however, since the damage has already been done.
Edit:
I have written the following function to recursively "grep" all strings in an object, and applied it to a dumped email object using the following keywords:
regex = "(?i)((marked)|(seen)|(unread)|(read)|(flag)|(delivered)|(status)|(sate))"
So far, no results (only an unrelated "Delivered-To"). Which other keywords could I try?
def grep_object (obj, regex , cycle = set(), matched = set()):
import re
if id(obj) in cycle:
return
cycle.update([id(obj)])
if isinstance(obj, basestring):
if re.search(regex, obj):
matched.update([obj])
def grep_dict (adict ):
try:
[ [ grep_object(a, regex, cycle, matched ) for a in ab ] for ab in adict.iteritems() ]
except:pass
grep_dict(obj)
try:grep_dict(obj.__dict__)
except:pass
try:
[ grep_object(elm, regex, cycle, matched ) for elm in obj ]
except: pass
return matched
grep_object(mail_object, regex)
I'm having a similar problem (not with gmail), and the biggest problem for me was to make a reproducible test case; and I finally managed to produce one (see below).
In terms of the Seen flag, I now gather it goes like this:
If a message is new/unseen, IMAP fetch for \Seen flag will return empty (i.e. it will not be present, as related to the email message).
If you do IMAP select on a mailbox (INBOX), you get a "flag" UNSEEN which contains a list of ids (or uids) of emails in that folder that are new (do not have the \Seen flag)
In my test case, if you fetch say headers for a message with BODY.PEEK, then \Seen on a message is not set; if you fetch them with BODY, then \Seen is set
In my test case, also fetching (RFC822) doesn't set \Seen (unlike your case with Gmail)
In the test case, I try to do pprint.pprint(inspect.getmembers(mail)) (in lieu of your dumpobj(uid, mail)) - but only after I'm certain \Seen has been set. The output I get is posted in mail_object_inspect.txt - and as far as I can see, there is no mention of 'new/read/seen' etc. in none of the readable fields; furthermore mail.as_string() prints:
'From: jesse#example.com\nTo: user#example.com\nSubject: This is a test message!\n\nHello. I am executive assistant to the director of\nBear Stearns, a failed investment Bank. I have\naccess to USD6,000,000. ...\n'
Even worse, there is no mention of "fields" anywhere in the imaplib code (below filenames are printed if they do not contain case-insensitive "field" anywhere):
$ grep -L -i field /usr/lib/python{2.7,3.2}/imaplib.py
/usr/lib/python2.7/imaplib.py
/usr/lib/python3.2/imaplib.py
... so I guess that information was not saved with your dumps.
Here is a bit on reconstructing the test case. The hardest was to find a small IMAP server, that can be quickly ran with some arbitrary users and emails, but without having to install a ton of stuff on your system. Finally I found one: trivial-server.pl, the example file of Perl's Net::IMAP::Server; tested on Ubuntu 11.04.
The test case is pasted in this gist, with two files (with many comments) that I'll try to post abridged:
trivial-serverB.pl - Perl (v5.10.1) Net::IMAP::Server server (has a terminal output paste at end of file with a telnet client session)
testimap.py - Python 2.7/3.2 imaplib
client (has a terminal output paste at end of file, of itself operating with the server)
trivial-serverB.pl
First, make sure you have Net::IMAP::Server - note, it has many dependencies, so the below command may take a while to install:
sudo perl -MCPAN -e 'install Net::IMAP::Server'
Then, in the directory where you got trivial-serverB.pl, create a subdirectory with SSL certificates:
mkdir certs
openssl req \
-x509 -nodes -days 365 \
-subj '/C=US/ST=Oregon/L=Portland/CN=localhost' \
-newkey rsa:1024 -keyout certs/server-key.pem -out certs/server-cert.pem
Finally run the server with administrative properties:
sudo perl trivial-serverB.pl
Note that the trivial-serverB.pl has a hack which will let a client to connect without SSL. Here is trivial-serverB.pl:
#!/usr/bin/perl
use v5.10.1;
use feature qw(say);
use Net::IMAP::Server;
package Demo::IMAP::Hack;
$INC{'Demo/IMAP/Hack.pm'} = 1;
sub capabilityb {
my $self = shift;
print STDERR "Capabilitin'\n";
my $base = $self->server->capability;
my #words = split " ", $base;
#words = grep {$_ ne "STARTTLS"} #words
if $self->is_encrypted;
unless ($self->auth) {
my $auth = $self->auth || $self->server->auth_class->new;
my #auth = $auth->sasl_provides;
# hack:
#unless ($self->is_encrypted) {
# # Lack of encrpytion makes us turn off all plaintext auth
# push #words, "LOGINDISABLED";
# #auth = grep {$_ ne "PLAIN"} #auth;
#}
push #words, map {"AUTH=$_"} #auth;
}
return join(" ", #words);
}
package Demo::IMAP::Auth;
$INC{'Demo/IMAP/Auth.pm'} = 1;
use base 'Net::IMAP::Server::DefaultAuth';
sub auth_plain {
my ( $self, $user, $pass ) = #_;
# XXX DO AUTH CHECK
$self->user($user);
return 1;
}
package Demo::IMAP::Model;
$INC{'Demo/IMAP/Model.pm'} = 1;
use base 'Net::IMAP::Server::DefaultModel';
sub init {
my $self = shift;
$self->root( Demo::IMAP::Mailbox->new() );
$self->root->add_child( name => "INBOX" );
}
###########################################
package Demo::IMAP::Mailbox;
use base qw/Net::IMAP::Server::Mailbox/;
use Data::Dumper;
my $data = <<'EOF';
From: jesse#example.com
To: user#example.com
Subject: This is a test message!
Hello. I am executive assistant to the director of
Bear Stearns, a failed investment Bank. I have
access to USD6,000,000. ...
EOF
my $msg = Net::IMAP::Server::Message->new($data);
sub load_data {
my $self = shift;
$self->add_message($msg);
}
my %ports = ( port => 143, ssl_port => 993 );
$ports{$_} *= 10 for grep {$> > 0} keys %ports;
$myserv = Net::IMAP::Server->new(
auth_class => "Demo::IMAP::Auth",
model_class => "Demo::IMAP::Model",
user => 'nobody',
log_level => 3, # at least 3 to output 'CONNECT TCP Peer: ...' message; 4 to output IMAP commands too
%ports,
);
# apparently, this overload MUST be after the new?! here:
{
no strict 'refs';
*Net::IMAP::Server::Connection::capability = \&Demo::IMAP::Hack::capabilityb;
}
# https://stackoverflow.com/questions/27206371/printing-addresses-of-perl-object-methods
say " -", $myserv->can('validate'), " -", $myserv->can('capability'), " -", \&Net::IMAP::Server::Connection::capability, " -", \&Demo::IMAP::Hack::capabilityb;
$myserv->run();
testimap.py
With the server above running in one terminal, in another terminal you can just do:
python testimap.py
The code will simply read fields and content from the one (and only) message the server above presents, and will eventually restore (remove) the \Seen field.
import sys
if sys.version_info[0] < 3: # python 2.7
def uttc(x):
return x
else: # python 3+
def uttc(x):
return x.decode("utf-8")
import imaplib
import email
import pprint,inspect
imap_user = 'nobody'
imap_password = 'whatever'
imap_server = 'localhost'
conn = imaplib.IMAP4(imap_server)
conn.debug = 3
try:
(retcode, capabilities) = conn.login(imap_user, imap_password)
except:
print(sys.exc_info()[1])
sys.exit(1)
# not conn.select(readonly=1), else we cannot modify the \Seen flag later
conn.select() # Select inbox or default namespace
(retcode, messages) = conn.search(None, '(UNSEEN)')
if retcode == 'OK':
for num in uttc(messages[0]).split(' '):
if not(num):
print("No messages available: num is `{0}`!".format(num))
break
print('Processing message: {0}'.format(num))
typ, data = conn.fetch(num,'(FLAGS)')
isSeen = ( "Seen" in uttc(data[0]) )
print('Got flags: {2}: {0} .. {1}'.format(typ,data, # NEW: OK .. ['1 (FLAGS ())']
"Seen" if isSeen else "NEW"))
print('Peeking headers, message: {0} '.format(num))
typ, data = conn.fetch(num,'(BODY.PEEK[HEADER])')
pprint.pprint(data)
typ, data = conn.fetch(num,'(FLAGS)')
isSeen = ( "Seen" in uttc(data[0]) )
print('Got flags: {2}: {0} .. {1}'.format(typ,data, # NEW: OK .. ['1 (FLAGS ())']
"Seen" if isSeen else "NEW"))
print('Get RFC822 body, message: {0} '.format(num))
typ, data = conn.fetch(num,'(RFC822)')
mail = email.message_from_string(uttc(data[0][1]))
#pprint.pprint(inspect.getmembers(mail))
typ, data = conn.fetch(num,'(FLAGS)')
isSeen = ( "Seen" in uttc(data[0]) )
print('Got flags: {2}: {0} .. {1}'.format(typ,data, # NEW: OK .. ['1 (FLAGS ())']
"Seen" if isSeen else "NEW"))
print('Get headers, message: {0} '.format(num))
typ, data = conn.fetch(num,'(BODY[HEADER])') # note, FLAGS (\\Seen) is now in data, even if not explicitly requested!
pprint.pprint(data)
print('Get RFC822 body, message: {0} '.format(num))
typ, data = conn.fetch(num,'(RFC822)')
mail = email.message_from_string(uttc(data[0][1]))
pprint.pprint(inspect.getmembers(mail)) # this is in mail_object_inspect.txt
pprint.pprint(mail.as_string())
typ, data = conn.fetch(num,'(FLAGS)')
isSeen = ( "Seen" in uttc(data[0]) )
print('Got flags: {2}: {0} .. {1}'.format(typ,data, # Seen: OK .. ['1 (FLAGS (\\Seen))']
"Seen" if isSeen else "NEW"))
conn.select() # select again, to see flags server side
# * OK [UNSEEN 0] # no more unseen messages (if there was only one msg in folder)
print('Restoring flag to unseen/new, message: {0} '.format(num))
ret, data = conn.store(num,'-FLAGS','\\Seen')
if ret == 'OK':
print("Set back to unseen; Got OK: {0}{1}{2}".format(data,'\n',30*'-'))
print(mail)
typ, data = conn.fetch(num,'(FLAGS)')
isSeen = ( "Seen" in uttc(data[0]) )
print('Got flags: {2}: {0} .. {1}'.format(typ,data, # NEW: OK .. [b'1 (FLAGS ())']
"Seen" if isSeen else "NEW"))
conn.close()
References
How do I mock an IMAP server in Python, despite extreme laziness?
Get only NEW Emails imaplib and python
Undoing "marked as read" status of emails fetched with imaplib
http://www.skytale.net/blog/archives/23-Manual-IMAP.html
IMAP FETCH Subject
https://mail.python.org/pipermail/python-list/2009-March/527020.html
http://www.thecodingforums.com/threads/re-imaplib-fetch-message-flags.673872/