imaplib2
This module defines a class, IMAP4
,
which encapsulates a threaded connection to an IMAP4 server
and implements the IMAP4rev1 client protocol
as defined in RFC 3501 with several extensions.
This module presents an almost identical API
as that provided by the standard python library module imaplib
,
the main difference being that this version
allows parallel execution of commands on the IMAP4 server,
and implements the IMAP4rev1 IDLE
extension.
(imaplib2
can be substituted for imaplib
in existing clients with no changes in the code, but see the caveat below.)
An IMAP4
instance is instantiated with an optional host
and/or port
. The defaults are localhost
and 143
- the standard IMAP4 port number.
There are also five other optional arguments:
debug=level, debug_file=file, identifier=string, timeout=seconds, debug_buf_lvl=level
.
Setting debug
level (default: 0) to anything above debug_buf_lvl
(default: 3)
causes every action to be printed to file (default: sys.stderr).
Otherwise actions are logged in a circular buffer and the last 20 printed on errors.
The third argument provides a string to be prepended to thread names - useful during debugging (default: target host).
The forth argument sets a timeout for responses from the server, after which the instance will abort.
Note that this timeout is overridden by an IDLE timeout when active.
Caveat: Once an instance has been created,
the invoker must call the logout
method before discarding it, to shut down the threads.
There are two classes derived from IMAP4
which provide alternate transport mechanisms:
IMAP4_SSL
IMAP4_stream
There are also 2 utility methods provided for processing IMAP4 date strings:
Internaldate2Time
(datestr)INTERNALDATE
string to Universal Time.
Returns a time
module tuple.Time2Internaldate
(date_time)date_time
(a time
module tuple,
or an integer or float seconds)
to an IMAP4 INTERNALDATE
representation.
Returns a string in the form:"DD-Mmm-YYYY HH:MM:SS +HHMM"
(including double-quotes).And there is one utility method for parsing IMAP4 FLAGS
responses:
ParseFlags
(response)"...FLAGS (flag ...)"
)
to a python tuple..All IMAP4rev1 commands are represented by methods of the same name
Each command returns a tuple: (type, [data, ...])
where type
is usually 'OK'
or 'NO'
,
and data
is either the
text from the command response
(always true when type
is 'NO'
),
or mandated results from the command.
Each data
is either a string, or a tuple.
If a tuple, then the first part is the
header of the response, and the second part contains the data (ie: literal value).
Any logical errors raise the exception class
<instance>.error("<reason>")
.
IMAP4 server errors raise <instance>.abort("<reason>")
,
which is a sub-class of error
.
Mailbox status changes from READ-WRITE
to READ-ONLY
raise <instance>.readonly("<reason>")
,
which is a sub-class of abort
.
Note that closing the instance and instantiating a new one
will usually recover from an abort
.
All commands take two optional named arguments:
callback
and cb_arg
.
If callback
is provided then the command is asynchronous
(the IMAP4 command is scheduled, and the call returns immediately),
and the result will be posted by invoking callback
with a single argument:
callback(((type, [data, ...]), cb_arg, None))
callback((None, cb_arg, (exception class, reason)))
.
Otherwise the command is synchronous (waits for result). But note that state-changing commands will both block until previous commands have completed, and block subsequent commands until they have finished.
All (non-callback) arguments to commands are converted to strings,
except for authenticate
,
and the last argument to append
which is passed as an IMAP4 literal.
If necessary
(the string contains any
non-printing characters or white-space and isn't enclosed with either
parentheses or double quotes or single quotes)
each string is quoted.
However, the password argument to the login
command is always quoted.
If you want to avoid having an argument string quoted
(eg: the flags argument to store
)
then enclose the string in parentheses (eg: (\Deleted)
).
If you are using sequence sets
containing the wildcard character '*', then enclose the argument
in single quotes: the quotes will be removed and the resulting
string passed unquoted.
To summarise the quoting rules:
login
command is always quoted;
"..."
or (...)
is passed as is;
'...'
is stripped of the enclosing single
quotes and the rest passed as is.
Note also that you can pass in an argument with a type that doesn't evaluate to basestring
(eg: bytearray
) and it will be converted to a string without quoting.
There is one instance variable, state
, that is useful for tracking
whether the client needs to login to the server. If it has the
value "AUTH"
after instantiating the class, then the connection
is pre-authenticated (otherwise it will be "NONAUTH"
). Selecting a
mailbox changes the state to be "SELECTED"
, closing a mailbox changes
back to "AUTH"
, and once the client has logged out, the state changes
to "LOGOUT"
and no further commands may be issued.
There is another instance variable, capabilities
,
that holds a list of the capabilities provided by the server (the
same as the list returned by the IMAP4 CAPABILITY
command).
An IMAP4
instance has the following methods:
append
(mailbox, flags, date_time, message)message
can be Noneauthenticate
(mechanism, authobject)<instance>.capabilities
in the
form AUTH=
mechanism.
data = authobject(response)
*
should
be sent instead.capability
()check
()close
()copy
(message_set, new_mailbox)create
(mailbox)delete
(mailbox)enable
(capability)if 'ENABLE' in imapobj.capabilities: imapobj.enable("UTF8=ACCEPT")
enable_compression
()if 'COMPRESS=DEFLATE' in imapobj.capabilities: imapobj.enable_compression()
examine
(mailbox='INBOX')'FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'
,
so other responses should be obtained by calling response('FLAGS')
etc.expunge
()fetch
(message_set, message_parts)message_parts
should be a string of selected parts
enclosed in parentheses, eg: "(UID BODY[TEXT])"
.
Returned data are tuples of message part envelope and data,
followed by a string containing the trailer.getacl
(mailbox)getannotation
(mailbox_name, entry_specifier, attribute_specifier)getquota
(root)getquotaroot
(mailbox)id
(field1, value1, ...)idle
(timeout=None
)list
(directory='""'
, pattern='*'
)login
(user, password)login_cram_md5
(user, password)logout
()lsub
(directory='""'
, pattern='*'
)myrights
(mailbox)namespace
()noop
()partial
(message_num, message_part, start, length)pop_untagged_responses
()proxyauth
(user)recent
()None
if no new messages,
else list of RECENT responses, most recent last.rename
(oldmailbox, newmailbox)response
(code)search
(charset, criterium, ...)select
(mailbox='INBOX', readonly=False)'FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'
,
so other responses should be obtained by calling response('FLAGS')
etc.setacl
(mailbox, who, what)setannotation
(mailbox_name, entry, attribute_value[, entry, attribute_value]*)setquota
(root, limits)sort
(sort_criteria, charset, search_criteria, ...)starttls
(keyfile, certfile, ca_certs, cert_verify_cb, ssl_version="ssl23", tls_level="tls_compat")starttls
should be called from user of the IMAP4 class after instantiation, as in:
if 'STARTTLS' in imapobj.capabilities: imapobj.starttls()
The recognized values for tls_level
are:
status
(mailbox, names)store
(message_set, command, flag_list)subscribe
(mailbox)thread
(threading_algorithm, charset, search_criteria, ...)uid
(command, arg, ...)command arg ...
with messages identified by UID,
rather than message number.
Returns response appropriate to command.unsubscribe
(mailbox)xatom
(command, arg, ...)IMAP4
instances have a variable, PROTOCOL_VERSION
,
that is set to the most recent supported protocol in the CAPABILITY response.
Here is a minimal example (without error checking) that opens a mailbox and retrieves and prints all messages:
def cb(cb_arg_list): response, cb_arg, error = cb_arg_list typ, data = response if not data: return for field in data: if type(field) is not tuple: continue print('Message %s:\n%s\n' % (field[0].split()[0], field[1])) import getpass, imaplib2 M = imaplib2.IMAP4() M.LOGIN(getpass.getuser(), getpass.getpass()) M.SELECT(readonly=True) typ, data = M.SEARCH(None, 'ALL') for num in data[0].split(): M.FETCH(num, '(RFC822)', callback=cb) M.CLOSE() M.LOGOUT()
Note that IMAP4 message numbers change as the mailbox changes,
so it is highly advisable to use UIDs instead via the UID
command.
At the end of the module, there is a test section that contains a more extensive example of usage.
Documents describing the protocol, and sources and binaries for servers implementing it, can all be found at http://www.washington.edu/imap.