[Python-checkins] python/nondist/sandbox/mailbox mailbox.py, NONE, 1.1 test_mailbox.py, NONE, 1.1 docs.tex, NONE, 1.1 libmailbox.tex, 1.1, 1.2

gregorykjohnson@users.sourceforge.net gregorykjohnson at users.sourceforge.net
Tue Jul 26 22:47:51 CEST 2005


Update of /cvsroot/python/python/nondist/sandbox/mailbox
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv3755

Modified Files:
	libmailbox.tex 
Added Files:
	mailbox.py test_mailbox.py docs.tex 
Log Message:
Maildir, with tests.


--- NEW FILE: mailbox.py ---
#! /usr/bin/env python

"""Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes."""

import os
import time
import socket
import errno
import stat
import copy
import email
import email.Message
import email.Generator
import rfc822

__all__ = [ 'open', 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF',
            'Message', 'MaildirMessage', 'mboxMessage', 'MHMessage',
            'BabylMessage', 'MMDFMessage' ]


def open(path, format=None, factory=None):
    """Open a mailbox at the given path and return a Mailbox instance.""" 


class Mailbox:
    """A group of messages in a particular place."""

    def __init__(self, path, factory=None):
        """Initialize a Mailbox instance."""
        self._path = os.path.abspath(path)
        self._factory = factory

    def add(self, message):
        """Add message and return assigned key."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def remove(self, key):
        """Remove the keyed message; raise KeyError if it doesn't exist."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def __delitem__(self, key):
        self.remove(key)

    def discard(self, key):
        """If the keyed message exists, remove it."""
        try:
            self.remove(key)
        except KeyError:
            pass

    def __setitem__(self, key, message):
        """Replace the keyed message; raise KeyError if it doesn't exist."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def get(self, key, default=None):
        """Return the keyed message, or default if it doesn't exist."""
        try:
            return self.__getitem__(key)
        except KeyError:
            return default

    def __getitem__(self, key):
        """Return the keyed message; raise KeyError if it doesn't exist."""
        if self._factory is None:
            return self.get_message(key)
        else:
            return self._factory(self.get_file(key))

    def get_message(self, key):
        """Return a Message representation or raise a KeyError."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def get_string(self, key):
        """Return a string representation or raise a KeyError."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def get_file(self, key):
        """Return a file-like representation or raise a KeyError."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def iterkeys(self):
        """Return an iterator over keys."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def keys(self):
        """Return a list of keys."""
        return list(self.iterkeys())

    def itervalues(self):
        """Return an iterator over all messages."""
        for key in self.iterkeys():
            try:
                value = self[key]
            except KeyError:
                continue
            yield value

    def __iter__(self):
        return self.itervalues()

    def values(self):
        """Return a list of messages. Memory intensive."""
        return list(self.itervalues())

    def iteritems(self):
        """Return an iterator over (key, message) tuples."""
        for key in self.iterkeys():
            try:
                value = self[key]
            except KeyError:
                continue
            yield (key, value)

    def items(self):
        """Return a list of (key, message) tuples. Memory intensive."""
        return list(self.iteritems())

    def has_key(self, key):
        """Return True if the keyed message exists, False otherwise."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def __contains__(self, key):
        return self.has_key(key)

    def __len__(self):
        """Return a count of messages in the mailbox."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def clear(self):
        """Delete all messages."""
        for key in self.iterkeys():
            self.discard(key)

    def pop(self, key, default=None):
        """Delete the keyed message and return it, or default."""
        try:
            result = self[key]
        except KeyError:
            return default
        self.discard(key)
        return result

    def popitem(self):
        """Delete an arbitrary (key, message) pair and return it."""
        for key in self.iterkeys():
            return (key, self.pop(key))     # This is only run once.
        else:
            raise KeyError, "No messages in mailbox" 

    def update(self, arg=None):
        """Change the messages that correspond to certain keys."""
        if hasattr(arg, 'items'):
            source = arg.items()
        else:
            source = arg
        bad_key = False
        for key, message in source:
            try:
                self[key] = message
            except KeyError:
                bad_key = True
        if bad_key:
            raise KeyError, "No message with key(s)"

    def flush(self):
        """Write any pending changes to disk."""
        raise NotImplementedError, "Method must be implemented by subclass"

    def _dump_message(self, message, target):
        """Dump message contents to target file."""
        if isinstance(message, email.Message.Message):
            generator = email.Generator.Generator(target, False, 0)
            generator.flatten(message)
        elif isinstance(message, str):
            target.write(message)
        elif hasattr(message, 'read'):
            while True:
                buffer = message.read(4096)     # Buffer size is arbitrary.
                if buffer == "":
                    break
                target.write(buffer)
        else:
            raise TypeError, "Invalid message type"


class Maildir(Mailbox):
    """A qmail-style Maildir mailbox."""

    def __init__(self, dirname, factory=rfc822.Message):
        """Initialize a Maildir instance."""
        Mailbox.__init__(self, dirname, factory)
        if not os.path.exists(self._path):
            os.mkdir(self._path, 0700)
            os.mkdir(os.path.join(self._path, "tmp"), 0700)
            os.mkdir(os.path.join(self._path, "new"), 0700)
            os.mkdir(os.path.join(self._path, "cur"), 0700)
        self._toc = {}

    def add(self, message):
        """Add message and return assigned key."""
        tmp_file = self._create_tmp()
        try:
            self._dump_message(message, tmp_file)
        finally:
            tmp_file.close()
        if isinstance(message, MaildirMessage):
            subdir = message.get_subdir()
            suffix = ':' + message.get_info()
            if suffix == ':':
                suffix = ''
        else:
            subdir = 'new'
            suffix = ''
        uniq = os.path.basename(tmp_file.name).split(':')[0]
        dest = os.path.join(self._path, subdir, uniq + suffix)
        os.rename(tmp_file.name, dest)
        return uniq

    def remove(self, key):
        """Remove the keyed message; raise KeyError if it doesn't exist."""
        os.remove(os.path.join(self._path, self._lookup(key)))

    def discard(self, key):
        """If the keyed message exists, remove it."""
        # This overrides an inapplicable implementation in the superclass.
        try:
            self.remove(key)
        except KeyError:
            pass
        except OSError, e:
            if errno == errno.ENOENT:
                pass
            else:
                raise 

    def __setitem__(self, key, message):
        """Replace the keyed message; raise KeyError if it doesn't exist."""
        old_subpath = self._lookup(key)
        temp_key = self.add(message)
        temp_subpath = self._lookup(temp_key)
        if isinstance(message, MaildirMessage):
            # temp's subdir and suffix were specified by message.
            dominant_subpath = temp_subpath
        else:
            # temp's subdir and suffix were defaults from add().
            dominant_subpath = old_subpath
        subdir = os.path.split(dominant_subpath)[0]
        if dominant_subpath.find(':') != -1:
            suffix = ':' + dominant_subpath.split(':')[-1]
        else:
            suffix = ''
        self.discard(key)
        os.rename(os.path.join(self._path, temp_subpath),
                  os.path.join(self._path, subdir, key + suffix))

    def get_message(self, key):
        """Return a Message representation or raise a KeyError."""
        subpath = self._lookup(key)
        f = file(os.path.join(self._path, subpath), 'r')
        try:
            msg = MaildirMessage(f)
        finally:
            f.close()
        subdir, name = os.path.split(subpath)
        if subdir == 'cur':
            msg.set_subdir('cur')
            if name.find(':') != -1:
                msg.set_info(name.split(':')[-1])
        return msg

    def get_string(self, key):
        """Return a string representation or raise a KeyError."""
        f = file(os.path.join(self._path, self._lookup(key)), 'r')
        try:
            return f.read()
        finally:
            f.close()

    def get_file(self, key):
        """Return a file-like representation or raise a KeyError."""
        f = file(os.path.join(self._path, self._lookup(key)), 'r')
        return _ProxyFile(f)

    def iterkeys(self):
        """Return an iterator over keys."""
        self._refresh()
        for key in self._toc:
            try:
                self._lookup(key)
            except KeyError:
                continue
            yield key

    def has_key(self, key):
        """Return True if the keyed message exists, False otherwise."""
        self._refresh()
        return key in self._toc

    def __len__(self):
        """Return a count of messages in the mailbox."""
        self._refresh()
        return len(self._toc)

    def flush(self):
        """Write any pending changes to disk."""
        return  # Maildir changes are always written immediately.

    def list_folders(self):
        """Return a list of folder names."""
        result = []
        for entry in os.listdir(self._path):
            if len(entry) > 1 and entry[0] == '.' and \
               os.path.isdir(os.path.join(self._path, entry)):
                result.append(entry[1:])
        return result

    def open_folder(self, name):
        """Return a Maildir for the named folder, creating it if necessary."""
        path = os.path.join(self._path, '.' + name)
        maildirfolder_path = os.path.join(path, 'maildirfolder')
        result = Maildir(path)
        if not os.path.exists(maildirfolder_path):
            os.mknod(maildirfolder_path, 0600 | stat.S_IFREG)
        return result

    def remove_folder(self, name):
        """Delete the named folder, which must be empty."""
        path = os.path.join(self._path, '.' + name)
        for entry in os.listdir(os.path.join(path, 'new')) + \
                     os.listdir(os.path.join(path, 'cur')):
            if len(entry) < 1 or entry[0] != '.':
                raise IOError, "Folder '%s' contains message" % name
        for entry in os.listdir(path):
            if entry != 'new' and entry != 'cur' and entry != 'tmp' and \
               os.path.isdir(os.path.join(path, entry)):
                raise IOError, "Folder '%s' contains subdirectory '%s'" % \
                               (name, entry)
        for root, dirs, files in os.walk(path, topdown=False):
            for entry in files:
                os.remove(os.path.join(root, entry))
            for entry in dirs:
                os.rmdir(os.path.join(root, entry))
        os.rmdir(path)

    def clean(self):
        """Delete old files in "tmp"."""
        now = time.time()
        for entry in os.listdir(os.path.join(self._path, 'tmp')):
            path = os.path.join(self._path, 'tmp', entry)
            if now - os.stat(path).st_atime > 129600:   # 60 * 60 * 36
                os.remove(path)

    _count = 1  # This is used to generate unique file names.

    def _create_tmp(self):
        """Create a file in the tmp subdirectory and open and return it."""
        now = time.time()
        hostname = socket.gethostname()
        if '/' in hostname:
            hostname = hostname.replace('/', r'\057')
        if ':' in hostname:
            hostname = hostname.replace(':', r'\072')
        uniq = "%s.M%sP%sQ%s.%s" % (int(now), int(now % 1 * 1e6), os.getpid(),
                                    self._count, hostname)
        path = os.path.join(self._path, 'tmp', uniq)
        try:
            os.stat(path)
        except OSError, e:
            if e.errno == errno.ENOENT:
                self._count += 1
                return file(path, 'w+')
            else:
                raise
        else:
            raise Error, "Name clash prevented file creation: '%s'" % path

    def _refresh(self):
        """Update table of contents mapping."""
        self._toc = {}
        for subdir in ('new', 'cur'):
            for entry in os.listdir(os.path.join(self._path, subdir)):
                uniq = entry.split(':')[0]
                self._toc[uniq] = os.path.join(subdir, entry)

    def _lookup(self, key):
        """Use TOC to return subpath for given key, or raise a KeyError."""
        try:
            if os.path.exists(os.path.join(self._path, self._toc[key])):
                return self._toc[key]
        except KeyError:
            pass
        self._refresh()
        try:
            return self._toc[key]
        except KeyError:
            raise KeyError, "No message with key '%s'" % key


class Message(email.Message.Message):
    """Message with mailbox-format-specific properties."""

    def __init__(self, message=None):
        """Initialize a Message instance."""
        if isinstance(message, email.Message.Message):
            self._become_message(copy.deepcopy(message))
        elif isinstance(message, str):
            self._become_message(email.message_from_string(message))
        elif hasattr(message, "read"):
            self._become_message(email.message_from_file(message))
        elif message is None:
            email.Message.Message.__init__(self)
        else:
            raise TypeError, "Invalid message type"

    def _become_message(self, message):
        """Assume the non-format-specific state of message."""
        for name in ('_headers', '_unixfrom', '_payload', '_charset',
                     'preamble', 'epilogue', 'defects', '_default_type'):
            self.__dict__[name] = message.__dict__[name]

    def _explain_to(self, message):
        """Copy format-specific state to message insofar as possible."""
        if isinstance(message, Message):
            return  # There's nothing format-specific to explain.
        else:
            raise TypeError, "Cannot convert to specified type"


class MaildirMessage(Message):
    """Message with Maildir-specific properties."""

    def __init__(self, message=None):
        """Initialize a MaildirMessage instance."""
        Message.__init__(self, message)
        self._subdir = 'new'
        self._info = ''
        if isinstance(message, Message):
            message._explain_to(self)

    def get_subdir(self):
        """Return 'new' or 'cur'."""
        return self._subdir

    def set_subdir(self, subdir):
        """Set subdir to 'new' or 'cur'."""
        if subdir == 'new' or subdir == 'cur':
            self._subdir = subdir
        else:
            raise ValueError, "subdir must be 'new' or 'cur'"

    def get_flags(self):
        """Return as a string the flags that are set."""
        if len(self._info) > 2 and self._info[:2] == '2,':
            return self._info[2:]
        else:
            return ""

    def set_flags(self, flags):
        """Set the given flags and unset all others."""
        if self.get_subdir() == 'new':
            self.set_subdir('cur')
        self._info = '2,' + ''.join(sorted(flags))

    def add_flags(self, flags):
        """Set the given flags without changing others.""" 
        self.set_flags(''.join(set(self.get_flags()) | set(flags)))

    def remove_flags(self, flags):
        """Unset the given string flags (if set) without changing other."""
        if self.get_flags() != "":
            self.set_flags(''.join(set(self.get_flags()) - set(flags)))

    def get_info(self):
        """Get the message's "info" as a string."""
        return self._info

    def set_info(self, info):
        """Set the message's "info" string."""
        if self.get_subdir() == 'new':
            self.set_subdir('cur')
        if isinstance(info, str):
            self._info = info
        else:
            raise TypeError, "info must be a string"

    def _explain_to(self, message):
        """Copy Maildir-specific state to message insofar as possible."""
        if isinstance(message, Message):
            return
        # XXX convert to other formats as needed
        else:
            raise TypeError, "Cannot convert to specified type"


class _ProxyFile:
    """A read-only wrapper of a file."""

    def __init__(self, f, pos=None):
        """Initialize a _ProxyFile."""
        self._file = f
        if pos is None:
            self._pos = f.tell()
        else:
            self._pos = pos

    def read(self, size=None):
        """Read bytes."""
        return self._read(size, self._file.read)

    def readline(self, size=None):
        """Read a line."""
        return self._read(size, self._file.readline)

    def readlines(self, sizehint=None):
        """Read multiple lines."""
        result = []
        for line in self:
            result.append(line)
            if sizehint is not None:
                sizehint -= len(line)
                if sizehint <= 0:
                    break
        return result

    def __iter__(self):
        """Iterate over lines."""
        return iter(self.readline, "")

    def tell(self):
        """Return the position."""
        return self._pos

    def seek(self, offset, whence=0):
        """Change position."""
        if whence == 1:
            self._file.seek(self._pos)
        self._file.seek(offset, whence)
        self._pos = self._file.tell()

    def close(self):
        """Close the file."""
        del self._file

    def _read(self, size, read_method):
        """Read size bytes using read_method."""
        if size is None:
            size = -1
        self._file.seek(self._pos)
        result = read_method(size)
        self._pos = self._file.tell()
        return result


class _PartialFile(_ProxyFile):
    """A read-only wrapper of part of a file."""

    def __init__(self, f, start=None, stop=None):
        """Initialize a _PartialFile."""
        _ProxyFile.__init__(self, f, start)
        self._start = start
        self._stop = stop

    def tell(self):
        """Return the position with respect to start."""
        return _ProxyFile.tell(self) - self._start

    def seek(self, offset, whence=0):
        """Change position, possibly with respect to start or stop."""
        if whence == 0:
            self._pos = self._start
            whence = 1
        elif whence == 2:
            self._pos = self._stop
            whence = 1
        _ProxyFile.seek(self, offset, whence)

    def _read(self, size, read_method):
        """Read size bytes using read_method, honoring start and stop."""
        remaining = self._stop - self._pos
        if remaining <= 0:
            return ''
        if size is None or size < 0 or size > remaining:
            size = remaining
        return _ProxyFile._read(self, size, read_method)


class Error(Exception):
    """Raised for module-specific errors."""

--- NEW FILE: test_mailbox.py ---
import os
import time
import stat
import socket
import email
import email.Message
import rfc822
import re
import StringIO
from test import test_support
import unittest
import mailbox


class TestBase(unittest.TestCase):

    def _check_sample(self, msg):
        # Inspect a mailbox.Message representation of the sample message
        self.assert_(isinstance(msg, email.Message.Message))
[...985 lines suppressed...]
    "From":""""Gregory K. Johnson" <gkj at gregorykjohnson.com>""",
    "To":"gkj at gregorykjohnson.com",
    "Subject":"Sample message",
    "Mime-Version":"1.0",
    "Content-Type":"""multipart/mixed; boundary="NMuMz9nt05w80d4+\"""",
    "Content-Disposition":"inline",
    "User-Agent": "Mutt/1.5.9i" }

_sample_payloads = ("This is a sample message.\n\n-- \nGregory K. Johnson\n",
"H4sICM2D1UIAA3RleHQAC8nILFYAokSFktSKEoW0zJxUPa7wzJIMhZLyfIWczLzUYj0uAHTs\n3FYlAAAA\n")


def test_main():
    test_support.run_unittest(TestMaildir, TestMessage, TestMaildirMessage,
                              TestMessageConversion, TestProxyFile,
                              TestPartialFile)


if __name__ == '__main__':
    test_main()

--- NEW FILE: docs.tex ---
% Compile using mkhowto, e.g.:
% ../../../dist/src/Doc/tools/mkhowto --html docs.tex

\documentclass{manual}
\title{\module{mailbox} Module Documentation}
\begin{document}
\maketitle
\tableofcontents
\input{libmailbox}
\end{document}

Index: libmailbox.tex
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/mailbox/libmailbox.tex,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- libmailbox.tex	9 Jul 2005 20:15:25 -0000	1.1
+++ libmailbox.tex	26 Jul 2005 20:47:48 -0000	1.2
@@ -41,48 +41,53 @@
 \subsection{\class{Mailbox} objects}
 \label{mailbox-objects}
 
+\begin{classdesc*}{Mailbox}
+Represents a mailbox, which may be inspected and modified.
+\end{classdesc*}
+
 The \class{Mailbox} interface is dictionary-like, with small keys
 corresponding to messages. Keys are issued by the \class{Mailbox} instance
 with which they will be used and are only meaningful to that \class{Mailbox}
-instance. (\strong{Implementation note:} \class{mbox}, \class{MH},
-\class{Babyl}, and \class{MMDF} instances use integers as keys, and
-\class{Maildir} instances uses short strings.) A key continues to identify a
-message even if the corresponding message is modified by replacing it with
-another message. Because keys are issued by a \class{Mailbox} instance rather
+instance. A key continues to identify a message even if the corresponding
+message is modified, such as by replacing it with another message. Messages may
+be added to a \class{Mailbox} instance using the set-like method
+\method{add()}. Because keys are issued by a \class{Mailbox} instance rather
 than being chosen, the conventional method for adding an item to a mapping
-(assigning a value to a new key) cannot be used. Instead, messages may be
-added to a \class{Mailbox} instance using the set-like method \method{add()}.
-For uniformity, \method{remove()} and \method{discard()} are also available.
+(assigning a value to a new key) cannot be used. (\strong{Implementation note:}
+\class{mbox}, \class{MH}, \class{Babyl}, and \class{MMDF} instances use
+integers as keys, and \class{Maildir} instances use short strings.)
 
-\class{Mailbox} semantics differ from dictionary semantics in important ways.
-Each time a message is requested, a new message representation (typically a
-\class{Message} instance) is generated based upon the current state of the
-underlying message. Similarly, when a message representation is assigned into
-a \class{Mailbox} instance's mapping, the message representation's contents
-are copied into the mailbox. In neither case is a reference to the message
-representation kept by the \class{Mailbox} instance.
+\class{Mailbox} interface semantics differ from dictionary semantics in some
+ways. Each time a message is requested, a new message representation (typically
+a \class{Message} instance) is generated based upon the current state of the
+underlying message. The \class{Mailbox} instance does not reuse this
+representation or keep a reference to it. Similarly, when a message
+representation is assigned into a \class{Mailbox} instance's mapping, the
+message representation's contents are copied into the mailbox. In neither case
+is a reference to the message representation kept by the \class{Mailbox}
+instance.
 
-Also unlike dictionaries, the default \class{Mailbox} iterator iterates over
-message representations, not keys. Moreover, modification of a mailbox during
-iteration is safe and well-defined. Messages added to the mailbox after an
-iterator is created will not be seen by the iterator, and messages removed
-from the mailbox before the iterator yields them will be silently skipped. All
-three kinds of iterator---key, value, and item---have this behavior.
+The default \class{Mailbox} iterator iterates over message representations, not
+keys as dictionaries do. Moreover, modification of a mailbox during iteration
+is safe and well-defined. Messages added to the mailbox after an iterator is
+created will not be seen by the iterator. Messages removed from the mailbox
+before the iterator yields them will be silently skipped, though using a key
+from an iterator may result in a \exception{KeyError} exception if the
+corresponding message is subsequently removed.
 
-\class{Mailbox} itself is not intended to be instantiated. Rather, it is
-intended to define an interface and to be inherited from by format-specific
-subclasses. You may directly instantiate a subclass, or alternatively you may
-use the module-level \function{open()} function to instantiate the appropriate
-subclass.
+\class{Mailbox} itself is intended to define an interface and to be inherited
+from by format-specific subclasses but is not intended to be instantiated.
+Instead, you may directly instantiate a subclass or use the module-level
+\function{open()} function to do so.
 
-\begin{funcdesc}{open}{path\optional{, format, factory}}
+\begin{funcdesc}{open}{path\optional{, format\optional{, factory}}}
 Instantiate and return an instance of the appropriate \class{Mailbox} subclass
 for the mailbox at \var{path}. The mailbox is created if it does not already
 exist.
     
 If \var{format} is specified, it should be one of "Maildir", "mbox", "MH",
 "Babyl", or "MMDF" (case insensitive). If \var{format} is not specified, then
-the format of the mailbox will be automatically detected. Automatic detection
+the format of the mailbox is automatically detected. Automatic detection
 involves inspecting the mailbox at \var{path} if such a mailbox exists or if
 no such mailbox exists inspecting \var{path} itself and assuming Maildir
 format if \var{path} ends with the system's path separator (e.g., "/" or
@@ -93,7 +98,7 @@
 representation. If \var{factory} is not specified, \class{Message} instances
 are used to represent messages.
 
-For historic reasons, some subclasses of \class{Mailbox} take instantiation
+For historical reasons, some subclasses of \class{Mailbox} take instantiation
 arguments with different purposes, names, or default values. The
 \function{open()} function is intended to present a convenient, uniform
 interface for \class{Mailbox} instantiation while maintaining backward
@@ -110,126 +115,101 @@
 \class{email.Message.Message} instance, a string, or a file-like object. If
 \var{message} is an instance of the appropriate format-specific
 \class{Message} subclass (e.g., if it's an \class{mboxMessage} instance and
-this is an \class{mbox} instance), its format-specific information will be
-used. Otherwise, reasonable defaults for format-specific information will be
-used.
+this is an \class{mbox} instance), its format-specific information is used.
+Otherwise, reasonable defaults for format-specific information are used.
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{remove}{key}
-Delete the message corresponding to \var{key} from the mailbox. Raise a
-\exception{KeyError} exception if no such message exists.
+\methodline{__delitem__}{key}
+\methodline{discard}{key}
+Delete the message corresponding to \var{key} from the mailbox.
 
-The behavior of \method{discard()} may be preferred to that of
-\method{remove()} if the underlying mailbox format supports concurrent
-modification by other processes, as is the case for Maildir.
+If no such message exists, a \exception{KeyError} exception is raised if the
+method was called as \method{remove()} or \method{__delitem__()} and no
+exception is raised if the method was called as \method{discard()}. The
+behavior of \method{discard()} may be preferred if the underlying mailbox
+format supports concurrent modification by other processes.
 \end{methoddesc}
 
-\begin{methoddesc}[Mailbox]{discard}{key}
-Delete the message corresponding to \var{key} from the mailbox, or do nothing
-if no such message exists.
-\end{methoddesc}
+\begin{methoddesc}[Mailbox]{__setitem__}{key, message}
+Replace the message corresponding to \var{key} with the message represented by
+\var{message}. Raise a \exception{KeyError} exception if no message already
+corresponds to \var{key}.
 
-\begin{methoddesc}[Mailbox]{iterkeys}{}
-Return an iterator over all keys.
+As with \method{add()}, parameter \var{message} may be a \class{Message}
+instance, an \class{email.Message.Message} instance, a string, or a file-like
+object. If \var{message} is an instance of the appropriate format-specific
+\class{Message} subclass (e.g., if it's an \class{mboxMessage} instance and
+this is an \class{mbox} instance), its format-specific information is used.
+Otherwise, the format-specific information of the message that currently
+corresponds to \var{key} is left unchanged. 
 \end{methoddesc}
 
-\begin{methoddesc}[Mailbox]{keys}{}
-Return a list of all keys.
+\begin{methoddesc}[Mailbox]{iterkeys}{}
+\methodline{keys}{}
+Return an iterator over all keys if called as \method{iterkeys()} or return a
+list of keys if called as \method{keys()}.
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{itervalues}{}
-Return an iterator over representations of all messages. The messages are
-represented as \class{Message} instances unless a custom message factory was
-specified when the \class{Mailbox} instance was initialized.
-\end{methoddesc}
-
-\begin{methoddesc}[Mailbox]{values}{}
-Return a list of representations of all messages. The messages are represented
-as \class{Message} instances unless a custom message factory was specified
-when the \class{Mailbox} instance was initialized. \warning{This may require a
-large amount of memory.}
+\methodline{__iter__}{}
+\methodline{values}{}
+Return an iterator over representations of all messages if called as
+\method{itervalues()} or \method{__iter__()} or return a list of such
+representations if called as \method{values()}. The messages are represented as
+\class{Message} instances unless a custom message factory was specified when
+the \class{Mailbox} instance was initialized. \note{The behavior of
+\method{__iter__()} is unlike that of dictionaries, which iterate over keys.}
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{iteritems}{}
+\methodline{items}{}
 Return an iterator over (\var{key}, \var{message}) pairs, where \var{key} is a
-key and \var{message} is a message representation. The messages are
-represented as \class{Message} instances unless a custom message factory was
-specified when the \class{Mailbox} instance was initialized.
-\end{methoddesc}
-
-\begin{methoddesc}[Mailbox]{items}{}
-Return a list of (\var{key}, \var{message}) pairs, where \var{key} is a key
-and \var{message} is a message representation. The messages are represented as
-\class{Message} instances unless a custom message factory was specified when
-the \class{Mailbox} instance was initialized. \warning{This may require a
-large amount of memory.}
+key and \var{message} is a message representation, if called as
+\method{iteritems()} or return a list of such pairs if called as
+\method{items()}. The messages are represented as \class{Message} instances
+unless a custom message factory was specified when the \class{Mailbox} instance
+was initialized.
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{get}{key\optional{, default=None}}
-Return a representation of the message corresponding to \var{key}, or
-\var{default} if no such message exists. The message is represented as a
-\class{Message} instance unless a custom message factory was specified when
-the \class{Mailbox} instance was initialized.
+\methodline{__getitem__}{key}
+Return a representation of the message corresponding to \var{key}. If no such
+message exists, \var{default} is returned if the method was called as
+\method{get()} and a \exception{KeyError} exception is raised if the method was
+called as \method{__getitem__()}. The message is represented as a
+\class{Message} instance unless a custom message factory was specified when the
+\class{Mailbox} instance was initialized.
 \end{methoddesc}
 
-\begin{methoddesc}[Mailbox]{get_message}{key\optional{, default=None}}
+\begin{methoddesc}[Mailbox]{get_message}{key}
 Return a \class{Message} representation of the message corresponding to
-\var{key}, or \var{default} if no such message exists.
+\var{key}, or raise a \exception{KeyError} exception if no such message
+exists.
 \end{methoddesc}
 
-\begin{methoddesc}[Mailbox]{get_string}{key\optional{, default=None}}
+\begin{methoddesc}[Mailbox]{get_string}{key}
 Return a string representation of the message corresponding to \var{key}, or
-\var{default} if no such message exists.
+raise a \exception{KeyError} exception if no such message exists.
 \end{methoddesc}
 
-\begin{methoddesc}[Mailbox]{get_file}{key\optional{, default=None}}
+\begin{methoddesc}[Mailbox]{get_file}{key}
 Return a file-like representation of the message corresponding to \var{key},
-or \var{default} if no such message exists.
+or raise a \exception{KeyError} exception if no such message exists. This file
+should be closed once it is no longer needed.
 
 \note{Unlike other representations of messages, file-like representations are
 not independent of the \class{Mailbox} instance that created them or of the
-underlying mailbox. If the mailbox is modified, the file-like representation
-may become unusable. More specific documentation is provided by each
-subclass.}
+underlying mailbox, although their exact behavior is mailbox-format dependent.
+More specific documentation is provided by each subclass.}
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{has_key}{key}
+\methodline{__contains__}{}
 Return \code{True} if \var{key} corresponds to a message, \code{False}
 otherwise.
 \end{methoddesc}
 
-\begin{methoddesc}[Mailbox]{__iter__}{}
-Synonymous with \method{itervalues()}. \note{This is unlike the default
-iteration behavior of dictionaries.}
-\end{methoddesc}
-
-\begin{methoddesc}[Mailbox]{__getitem__}{key}
-Like \method{get()}, except raises a \exception{KeyError} exception if the
-message corresponding to \var{key} does not exist.
-\end{methoddesc}
-
-\begin{methoddesc}[Mailbox]{__setitem__}{key, message}
-Replace the message corresponding to \var{key} with the message represented by
-\var{message}. Raise a \exception{KeyError} exception if no message already
-corresponds to the given key.
-
-As with \method{add()}, parameter \var{message} may be a \class{Message}
-instance, an \class{email.Message.Message} instance, a string, or a file-like
-object. If \var{message} is an instance of the appropriate format-specific
-\class{Message} subclass (e.g., if it's an \class{mboxMessage} instance and
-this is an \class{mbox} instance), its format-specific information will be
-used. Otherwise, reasonable defaults for format-specific information will be
-used. 
-\end{methoddesc}
-
-\begin{methoddesc}[Mailbox]{__delitem__}{key}
-Synonymous with \method{remove()}.
-\end{methoddesc}
-
-\begin{methoddesc}[Mailbox]{__contains__}{key}
-Synonymous with \method{has_key()}.
-\end{methoddesc}
-
 \begin{methoddesc}[Mailbox]{__len__}{}
 Return a count of messages in the mailbox.
 \end{methoddesc}
@@ -240,34 +220,125 @@
 
 \begin{methoddesc}[Mailbox]{pop}{key\optional{, default}}
 Return a representation of the message corresponding to \var{key} and delete
-the message. If no such message exists, return \var{default} if it was
-supplied or if \var{default} was not supplied raise a \exception{KeyError}
-exception. The message is represented as a \class{Message} instance unless a
-custom message factory was specified when the \class{Mailbox} instance was
-initialized.
+the message. If no such message exists, return \var{default} if it was supplied
+or else raise a \exception{KeyError} exception. The message is represented as a
+\class{Message} instance unless a custom message factory was specified when the
+\class{Mailbox} instance was initialized.
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{popitem}{}
 Return an arbitrary (\var{key}, \var{message}) pair, where \var{key} is a key
 and \var{message} is a message representation, and delete the corresponding
-message from the mailbox. If the mailbox is empty, raise a
-\exception{KeyError} exception. The message is represented as a
-\class{Message} instance unless a custom message factory was specified when
-the \class{Mailbox} instance was initialized.
+message. If the mailbox is empty, raise a \exception{KeyError} exception. The
+message is represented as a \class{Message} instance unless a custom message
+factory was specified when the \class{Mailbox} instance was initialized.
 \end{methoddesc}
 
 \begin{methoddesc}[Mailbox]{update}{arg}
-If \var{arg} is a mailbox-like \var{key}-to-\var{message} mapping or an
-iterable of (\var{key}, \var{message}) pairs, update the mailbox so that, for
+Parameter \var{arg} should be a \var{key}-to-\var{message} mapping or an
+iterable of (\var{key}, \var{message}) pairs. Updates the mailbox so that, for
 each given \var{key} and \var{message}, the message corresponding to \var{key}
-is set to \var{message} as if by using \method{__setitem__()}. \note{Keyword
-arguments are not supported, unlike with dictionaries.}
+is set to \var{message} as if by using \method{__setitem__()}. Each \var{key}
+must already correspond to a message in the mailbox or a \exception{KeyError}
+exception will be raised. \note{Unlike with dictionaries, keyword arguments
+are not supported.}
+\end{methoddesc}
+
+\begin{methoddesc}[Mailbox]{flush}{}
+Write any pending changes to the filesystem. For some \class{Mailbox}
+subclasses, this is done automatically and calling \method{flush()} has no
+effect. More specific documentation is provided by each subclass.
 \end{methoddesc}
 
 
 \subsubsection{\class{Maildir}}
 \label{mailbox-maildir}
 
+\begin{classdesc}{Maildir}{dirname\optional{, factory}}
+A subclass of \class{Mailbox} for mailboxes in Maildir format. Parameter
+\var{dirname} is the path to the mailbox. Parameter \var{factory} has the same
+meaning as with the module-level \method{open()} function. For historical
+reasons, \var{factory} defaults to \class{rfc822.Message}.
+\end{classdesc}
+
+Maildir is a directory-based mailbox format invented for the qmail MTA and now
+widely supported by other programs. Messages in a Maildir mailbox are stored
+in separate files within a shared directory structure. This structure allows
+Maildir mailboxes to be accessed and modified by multiple unrelated programs
+without data corruption, so file locking is unnecessary.
+
+Folders, as introduced by the Courier MTA, are supported. Each folder is
+itself a Maildir mailbox and is represented as a \class{Maildir} instance. Any
+subdirectory of the main Maildir directory is considered a folder if
+\character{.} is the first character in its name. Folder names are represented
+without the leading dot. For example, "Sent.2005.07" would be the name of a
+folder implemented with a directory called ".Sent.2005.07" on the filesystem.
+
+\class{Maildir} instances have all of the methods of \class{Mailbox} in
+addition to the following:
+
+\begin{methoddesc}[Maildir]{list_folders}{}
+Return a list of the names of all folders. If there are no folders, the empty
+list is returned.
+\end{methoddesc}
+
+\begin{methoddesc}[Maildir]{open_folder}{name}
+Return a \class{Maildir} instance representing the folder whose name is
+\var{name}. The folder will be created if it does not exist.
+\end{methoddesc}
+
+\begin{methoddesc}[Maildir]{remove_folder}{name}
+Delete the folder whose name is \var{name}. If the folder contains any
+messages, an \exception{IOError} exception will be raised and the folder will
+not be deleted.
+\end{methoddesc}
+
+\begin{methoddesc}[Maildir]{clean}{}
+Delete temporary files from the mailbox that have not been accessed in the
+last 36 hours. The Maildir specification says that mail-reading programs
+should do this occassionally.
+\end{methoddesc}
+
+\warning{Three \class{Maildir} methods---\method{add()},
+\method{__setitem__()}, and \method{update()}---generate unique file names
+based upon the current process ID. When using multiple threads, undetected
+clashes may occur and cause corruption of the mailbox unless threads are
+coordinated to avoid using these methods to manipulate the same mailbox
+simultaneously.}
+
+Some \class{Mailbox} methods implemented by \class{Maildir} deserve special
+remarks: 
+
+\begin{methoddesc}[Maildir]{add}{message}
+\methodline[Maildir]{__setitem__}{key, message}
+\methodline[Maildir]{update}{arg}
+\warning{These methods generate unique file names based upon the current
+process ID. When using multiple threads, undetected name clashes may occur and
+cause corruption of the mailbox unless threads are coordinated to avoid using
+these methods to manipulate the same mailbox simultaneously.}
+\end{methoddesc}
+
+\begin{methoddesc}[Maildir]{flush}{}
+All changes to Maildir mailboxes are immediately applied. This method does
+nothing.
+\end{methoddesc}
+
+\begin{methoddesc}[Maildir]{get_file}{key}
+Depending upon the host platform, it may not be possible to use a
+\class{Maildir} instance to modify or remove the underlying message while the
+returned file remains open.
+\end{methoddesc}
+
+\begin{seealso}
+    \seelink{http://www.qmail.org/man/man5/maildir.html}{maildir man page from
+    qmail}{The original specification of the format.}
+    \seelink{http://cr.yp.to/proto/maildir.html}{Using maildir format}{Notes
+    on Maildir by it's inventor. Includes an updated name-creation scheme and
+    details on "info" semantics.}
+    \seelink{http://www.courier-mta.org/?maildir.html}{maildir man page from
+    Courier}{Another specification of the format. Describes a common extension
+    for supporting folders.}
+\end{seealso}
 
 \subsubsection{\class{mbox}}
 \label{mailbox-mbox}
@@ -288,10 +359,132 @@
 \subsection{\class{Message} objects}
 \label{mailbox-message-objects}
 
+The \class{Message} class is an extension of a class of the same name from the
+\module{email.Message} module. In addition, subclasses of \class{Message}
+support mailbox-format-specific state and behavior.
+
+\begin{classdesc}{Message}{\optional{message}}
+Represents a message with mailbox-format-specific properties.
+
+If \var{message} is omitted, the new instance is created in a default, empty
+state. If \var{message} is an \class{email.Message.Message} instance, its
+contents are copied, converting any format-specific information insofar as
+possible if \var{message} is a \class{Message} instance. If \var{message} is a
+string or a file, it should contain an \rfc{2822}-compliant message, which is
+read and parsed.
+\end{classdesc}
+
+The format-specific state and behaviors offered by subclasses vary, but in
+general it is only the properties that are not specific to a particular
+mailbox that are supported (although presumably the properties are specific to
+a particular mailbox format). For example, file offsets for single-file mailbox
+formats and file names for directory-based mailbox formats are not retained,
+but state such as whether a message has been read or marked as important by the
+user is.
+
+In some situations, the time and memory overhead involved in generating
+\class{Message} representations might not not justified. For such situations,
+\class{Mailbox} instances also offer string and file-like representations, and
+a custom message factory may be specified when a \class{Mailbox} instance is
+initialized. There is no requirement to use the \class{Message} class to
+represent messages from a mailbox.
+
+All of the \class{email.Message.Message} class's methods and members are
+supported by \class{Message}, and subclasses of \class{Message} provide many
+additional format-specific methods. Some functionality supported by all
+\class{Message} subclasses is accessible via the following methods:
+
+XXX
 
 \subsubsection{\class{MaildirMessage}}
 \label{mailbox-maildirmessage}
 
+\begin{classdesc}{MaildirMessage}{\optional{message}}
+Represents a message with Maildir-specific behaviors. Parameter \var{message}
+has the same meaning as with the \class{Message} constructor.
+\end{classdesc}
+
+Maildir messages are stored in individual files, in either the \file{new} or
+the \file{cur} subdirectory of the Maildir. Typically, messages are delivered
+to the \file{new} subdirectory and then moved to the \file{cur} subdirectory
+as soon as the user's mail reading application detects them. Each message in
+\file{cur} has an "info" section added to its file name to store information
+about its state. The "info" section may take one of two forms: it may contain
+a standardized list of flags or it may contain so-called experimental
+information.
+
+Standard flags for Maildir messages are as follows:
+
+\begin{tableiii}{l|l|l}{textrm}{Flag}{Meaning}{Explanation}
+\lineiii{D}{Draft}{Under composition}
+\lineiii{F}{Flagged}{Marked by the user as important}
+\lineiii{P}{Passed}{Forwarded, resent, or bounced by the user}
+\lineiii{R}{Replied}{Responded to}
+\lineiii{S}{Seen}{Read by the user}
+\lineiii{T}{Trashed}{Marked for subsequent deletion}
+\end{tableiii}
+
+\class{MaildirMessage} instances offer the following methods:
+
+\begin{methoddesc}[MaildirMessage]{get_subdir}{}
+Return either "new" (if the message should be stored in the \file{new}
+subdirectory) or "cur" (if the message should be stored in the \file{cur}
+subdirectory). \note{A Maildir message typically moves from \file{new} to
+\file{cur} when an MUA first detects it, but most MUAs refer to a message as
+new if it is unseen, i.e., if \code{"S" in get_flags()} is \code{False}.}
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{set_subdir}{subdir}
+Set the subdirectory the message should be stored in. Parameter \var{subdir}
+must be either "new" or "cur".
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{get_flags}{}
+Return a string specifying the flags that are currently set. If the message
+complies with the standard Maildir format, the result is the concatenation in
+alphabetical order of zero or one occurrence of each of \character{D},
+\character{F}, \character{P}, \character{R}, \character{S}, and \character{T}.
+The empty string is returned if no flags are set, if the message is new, or if
+"info" contains experimental semantics.
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{set_flags}{flags}
+Set the flags specified by \var{flags} and unset all others. Parameter
+\var{flags} should be the concatenation in any order of zero or more
+occurrences of each of \character{D}, \character{F}, \character{P},
+\character{R}, \character{S}, and \character{T}. If the message is new, it
+is made non-new. The current "info" is overwritten whether or not it contains
+experimental information instead of flags.
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{add_flags}{flags}
+Set the flags specified by \var{flags} (if they are not set), and don't change
+other flags. Parameter \var{flags} should be the concatenation in any order of
+zero or more occurrences of each of \character{D}, \character{F},
+\character{P}, \character{R}, \character{S}, and \character{T}. If the message
+is new, it is made non-new. If "info" contains experimental information rather
+than flags, the "info" will be overwritten.
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{remove_flags}{flags}
+Unset the flags specified by \var{flags} (if they are set), and don't change
+other flags. Parameter \var{flags} should be the concatenation in any order of
+zero or more occurrences of each of \character{D}, \character{F},
+\character{P}, \character{R}, \character{S}, and \character{T}. If the message
+is new, it remains so. If "info" contains experimental information rather than
+flags, the current "info" is not modified.
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{get_info}{}
+Return a string containing the "info" for a message. This is useful for
+accessing and modifying "info" that is experimental (i.e., not a list of
+flags).
+\end{methoddesc}
+
+\begin{methoddesc}[MaildirMessage]{set_info}{info}
+Set "info" to \var{info}, which should be a string. If the message is new, it
+is made non-new.
+\end{methoddesc}
 
 \subsubsection{\class{mboxMessage}}
 \label{mailbox-mboxmessage}



More information about the Python-checkins mailing list