Two annoyances I have with Python's imaplib module
As I mentioned yesterday, I recently wrote
some code that uses the imaplib
module. In the process
of doing this, I wound up experiencing some annoyances, one of them
a traditional one and one a new one that I've only come to appreciate
recently.
The traditional annoyance is that the imaplib
module doesn't wrap
errors from other modules that it uses. This leaves you with at
least two problems. The first is that you get to try to catch a
bunch of exception classes to handle errors:
try: c = ssl.create_default_context() m = imaplib.IMAP4_SSL(host=host, ssl_context=c) [...] except (imaplib.IMAP4.error, ssl.SSLError, OSError) as e: [...]
The second is that, well, I'm not sure I'm actually catching all
of the errors that calling the imaplib
module can raise. The
module doesn't document them, and so this list is merely the ones
that I've been able to provoke in testing. This is the fundamental
flaw of not wrapping exceptions that I wrote about many years ago; by not wrapping exceptions, you make what
modules you call an implicit part of your API. Then you usually
don't document it.
I award the imaplib module bonus points for having its error exception
class accessed via an attribute on another class. I'm sure there's
a historical reason for this, but I really wish it had been cleaned
up as part of the Python 3 migration. In the current Python 3
source,
these exception classes are actually literally classes inside the
IMAP4
class:
class IMAP4: [...] class error(Exception): pass class abort(error): pass class readonly(abort): pass [...]
The other annoyance is that the imaplib
module doesn't implement
any sort of timeouts, either on individual operations or on a whole
sequence of them. If you aren't prepared to wait for potentially
very long amounts of time (if the IMAP server has something go wrong
with it), you need to add some sort of timeout yourself through
means outside of imaplib
, either something like signal.setitimer()
with a SIGALRM
handler or through manipulating the underlying
socket to set timeouts on it (although I've read that this causes
problems, and anyway you're normally going to be trying to work
through SSL as well). For my own program I opted to go the SIGALRM
route, but I have the advantage that the only thing I'm doing is
IMAP. A more sophisticated program might not want to blow itself
up with a SIGALRM
just because the IMAP side of things was too
slow.
Timeouts aren't something that I used to think about when I wrote
programs that were mostly run interactively and did only one thing,
where the timeout is most sensibly imposed by the user hitting
Ctrl-C to kill the entire program. Automated testing programs and
other, similar things care a lot about timeouts, because they don't
want to hang if something goes wrong with the server. And in fact
it is possible to cause imaplib
to hang for a quite long time in
a very simple way:
m = imaplib.IMAP4_SSL(host=host, port=443)
You don't even need something that actually responds and gets as far as establishing a TLS session; it's enough for the TCP connection to be accepted. This is reasonably dangerous, because 'accept the connection and then hang' is more or less the expected behavior for a system under sufficiently high load (accepting the connection is handled in the kernel, and then the system is too loaded for the IMAP server to run).
Overall I've wound up feeling that the imaplib
module is okay for
simple, straightforward uses but it's not really a solid base for
anything more. Sure, you can probably use it, but you're also
probably going to be patching things and working around issues.
For us, using imaplib
and papering over these issues is the easiest
way forward, but if I wanted to do more I'd probably look for a third
party module (or think about switching languages).
|
|