Changeset 249:456de0713e01

Show
Ignore:
Timestamp:
05/06/11 17:24:20 (12 months ago)
Author:
Menno Smits <menno@…>
Branch:
default
Parents:
248:bbef196a11da (diff), 234:1be67b3e0445 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Synced sphinx branch with current trunk

Files:
2 modified

Legend:

Unmodified
Added
Removed
  • imapclient/imapclient.py

    r226 r249  
    5656class IMAPClient(object): 
    5757    """ 
    58     A Pythonic, easy-to-use IMAP client class. 
    59  
    60     Unlike imaplib, arguments and returns values are Pythonic and readily 
    61     usable. Exceptions are raised when problems occur (no error checking of 
    62     return values is required). 
    63  
    64     Message unique identifiers (UID) can be used with any call. The use_uid 
    65     argument to the constructor and the use_uid attribute control whether UIDs 
    66     are used. 
    67  
    68     Any method that accepts message id's takes either a sequence containing 
    69     message IDs (eg. [1,2,3]) or a single message ID as an integer. 
    70  
    71     Any method that accepts message flags takes either a sequence containing 
    72     message flags (eg. [DELETED, 'foo', 'Bar']) or a single message flag (eg. 
    73     'Foo'). See the constants at the top of this file for commonly used flags. 
    74  
    75     Any method that takes a folder name will accept a standard string or a 
    76     unicode string. Unicode strings will be transparently encoded using 
    77     modified UTF-7 as specified by RFC-2060. Such folder names will be returned 
    78     as unicode strings by methods that return folder names. 
    79  
    80     Transparent folder name encoding can be enabled or disabled with the 
    81     folder_encode attribute. It defaults to True. 
    82  
    83     The IMAP related exceptions that will be raised by this class are: 
    84         IMAPClient.Error 
    85         IMAPClient.AbortError 
    86         IMAPClient.ReadOnlyError 
    87     These are aliases for the imaplib.IMAP4 exceptions of the same name. Socket 
    88     errors may also be raised in the case of network errors. 
     58    A connection to the IMAP server specified by *host* is made when 
     59    the class is instantiated. 
     60 
     61    *port* defaults to 143, or 993 if *ssl* is ``True``. 
     62 
     63    If *use_uid* is ``True`` unique message UIDs be used for all calls 
     64    that accept message ids (defaults to ``True``). 
     65 
     66    If *ssl* is ``True`` an SSL connection will be made (defaults to 
     67    ``False``). 
    8968    """ 
    9069 
     
    9776 
    9877    def __init__(self, host, port=None, use_uid=True, ssl=False): 
    99         """Initialise object instance and connect to the remote IMAP server. 
    100  
    101         @param host: The IMAP server address/hostname to connect to. 
    102         @param port: The port number to use (default is 143, 993 for SSL). 
    103         @param use_uid: Should message UIDs be used (default is True). 
    104         @param ssl: Make an SSL connection (default is False) 
    105         """ 
    10678        if port is None: 
    10779            port = ssl and 993 or 143 
     
    12496 
    12597    def login(self, username, password): 
    126         """Perform a simple login 
     98        """Login using *username* and *password*, returning the 
     99        server response. 
    127100        """ 
    128101        typ, data = self._imap.login(username, password) 
     
    152125 
    153126    def logout(self): 
    154         """Perform a logout 
     127        """Logout, returning the server response. 
    155128        """ 
    156129        typ, data = self._imap.logout() 
     
    160133 
    161134    def capabilities(self): 
    162         """Returns the server capability list 
     135        """Returns the server capability list. 
    163136        """ 
    164137        return self._imap.capabilities 
     
    166139 
    167140    def has_capability(self, capability): 
    168         """Checks if the server has the given capability. 
    169  
    170         @param capability: capability to test (eg 'SORT') 
     141        """Return ``True`` if the IMAP server has the given *capability*. 
    171142        """ 
    172143        # FIXME: this will not detect capabilities that are backwards 
     
    181152 
    182153    def namespace(self): 
    183         """Return the namespace for the account as a (personal, other, shared) tuple. 
     154        """Return the namespace for the account as a (personal, other, 
     155        shared) tuple. 
    184156 
    185157        Each element may be None if no namespace of that type exists, 
     
    187159 
    188160        For convenience the tuple elements may be accessed 
    189         positionally or attributes named "personal", "other" and 
    190         "shared". 
     161        positionally or using attributes named *personal*, *other* and 
     162        *shared*. 
    191163 
    192164        See RFC 2342 for more details. 
     
    197169 
    198170    def get_folder_delimiter(self): 
    199         """Determine the folder separator used by the IMAP server. 
    200  
    201         WARNING: The implementation just picks the first folder 
    202         separator from the first namespace returned. This is not 
    203         particularly sensible. Use namespace instead(). 
    204  
    205         @return: The folder separator. 
    206         @rtype: string 
     171        """Return the folder separator used by the IMAP server. 
     172 
     173        .. warning:: 
     174 
     175            The implementation just picks the first folder separator 
     176            from the first namespace returned. This is not 
     177            particularly sensible. Use namespace instead(). 
    207178        """ 
    208179        warnings.warn(DeprecationWarning('get_folder_delimiter is going away. Use namespace() instead.')) 
     
    213184 
    214185    def list_folders(self, directory="", pattern="*"): 
    215         """Get a listing of folders on the server. 
    216  
    217         The default behaviour (no args) will list all folders for the logged in 
    218         user. 
    219  
    220         @param directory: The base directory to look for folders from. 
    221         @param pattern: A pattern to match against folder names. Only folder 
    222             names matching this pattern will be returned. Wildcards accepted. 
    223         @return: A list of (flags, delim, folder_name). Each folder name will 
    224             be either a string or a unicode string (if the folder on the 
    225             server required decoding). If the folder_encode attribute is 
    226             False, no decoding will be performed and only ordinary strings 
    227             will be returned. 
     186        """Get a listing of folders on the server as a list of 
     187        ``(flags, delimiter, name)`` tuples. 
     188 
     189        Calling list_folders with no arguments will list all 
     190        folders. 
     191 
     192        Specifying *directory* will limit returned folders to that 
     193        base directory. Specifying *pattern* will limit returned 
     194        folders to those with matching names. The wildcards are 
     195        supported in *pattern*. ``*`` matches zero or more of any 
     196        character and ``%`` matches 0 or more characters except the 
     197        folder delimiter. 
     198 
     199        Folder names are always returned as unicode strings except if 
     200        folder_decode is not set. 
    228201        """ 
    229202        return self._do_list('LIST', directory, pattern) 
    230203 
    231204    def xlist_folders(self, directory="", pattern="*"): 
    232         """A gmail-specific IMAP extension. 
    233          
    234         This method returns special flags for each folder and a localized name 
    235         for certain folders (eg, the name of the 'inbox' may be localized as the 
    236         flags can be used to determine the actual inbox, even if the name has 
    237         been localized.  It is the responsibility of the caller to either check 
    238         for 'XLIST' in the server capabilites, or to handle the error if the 
    239         server doesn't support this externsion. 
    240  
    241         @param directory: The base directory to look for folders from. 
    242         @param pattern: A pattern to match against folder names. Only folder 
    243             names matching this pattern will be returned. Wildcards accepted. 
    244         @return: A list of (flags, delim, folder_name). As per the return of 
    245             list_folders(). 
     205        """Execute the XLIST command, returning ``(flags, delimiter, 
     206        name)`` tuples. 
     207 
     208        This method returns special flags for each folder and a 
     209        localized name for certain folders (e.g. the name of the 
     210        inbox may be localized and the flags can be used to 
     211        determine the actual inbox, even if the name has been 
     212        localized. 
     213 
     214        A ``XLIST`` response could look something like:: 
     215 
     216            [([u'\\HasNoChildren', u'\\Inbox'], '/', u'Inbox'), 
     217             ([u'\\Noselect', u'\\HasChildren'], '/', u'[Gmail]'), 
     218             ([u'\\HasNoChildren', u'\\AllMail'], '/', u'[Gmail]/All Mail'), 
     219             ([u'\\HasNoChildren', u'\\Drafts'], '/', u'[Gmail]/Drafts'), 
     220             ([u'\\HasNoChildren', u'\\Important'], '/', u'[Gmail]/Important'), 
     221             ([u'\\HasNoChildren', u'\\Sent'], '/', u'[Gmail]/Sent Mail'), 
     222             ([u'\\HasNoChildren', u'\\Spam'], '/', u'[Gmail]/Spam'), 
     223             ([u'\\HasNoChildren', u'\\Starred'], '/', u'[Gmail]/Starred'), 
     224             ([u'\\HasNoChildren', u'\\Trash'], '/', u'[Gmail]/Trash')] 
     225 
     226        This is a Gmail-specific IMAP extension. It is the 
     227        responsibility of the caller to either check for ``XLIST`` in 
     228        the server capabilites, or to handle the error if the server 
     229        doesn't support this externsion. 
     230 
     231        The *directory* and *pattern* arguments are as per 
     232        list_folders(). 
    246233        """ 
    247234        return self._do_list('XLIST', directory, pattern) 
    248235 
    249236    def list_sub_folders(self, directory="", pattern="*"): 
    250         """Get a listing of subscribed folders on the server. 
    251  
    252         The default behaviour (no args) will list all subscribed folders for the 
    253         logged in user. 
    254  
    255         @param directory: The base directory to look for folders from. 
    256         @param pattern: A pattern to match against folder names. Only folder 
    257             names matching this pattern will be returned. Wildcards accepted. 
    258         @return: A list of (flags, delim, folder_name). As per the return of 
    259             list_folders(). 
     237        """Return a list of subscribed folders on the server as 
     238        ``(flags, delimiter, name)`` tuples. 
     239 
     240        The default behaviour will list all subscribed folders. The 
     241        *directory* and *pattern* arguments are as per list_folders(). 
    260242        """ 
    261243        return self._do_list('LSUB', directory, pattern) 
     
    283265 
    284266    def select_folder(self, folder, readonly=False): 
    285         """Select the current folder on the server. Future calls to methods 
    286         such as search and fetch will act on the selected folder. 
    287  
    288         @param folder: The folder name. 
    289         @return: A dictionary containing the SELECT response 
    290           values. At least the EXISTS, FLAGS and RECENT keys are 
    291           guaranteed to exist. Example: 
     267        """Set the current folder on the server. 
     268 
     269        Future calls to methods such as search and fetch will act on 
     270        the selected folder. 
     271 
     272        Returns a dictionary containing the ``SELECT`` response. At least 
     273        the ``EXISTS``, ``FLAGS`` and ``RECENT`` keys are guaranteed 
     274        to exist. An example:: 
     275 
    292276            {'EXISTS': 3, 
    293277             'FLAGS': ('\\Answered', '\\Flagged', '\\Deleted', ... ), 
     
    406390 
    407391    def folder_status(self, folder, what=None): 
    408         """Requests the status from folder. 
    409  
    410         @param folder: The folder name. 
    411         @param what: A sequence of status items to query. Defaults to 
    412             ('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'). 
    413         @return: Dictionary of the status items for the folder. The keys match 
    414             the items specified in the what parameter. 
    415         @rtype: dict 
     392        """Return the status of *folder*. 
     393 
     394        *what* should be a sequence of status items to query. This 
     395        defaults to ``('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 
     396        'UNSEEN')``. 
     397 
     398        Returns a dictionary of the status items for the folder with 
     399        keys matching *what*. 
    416400        """ 
    417401        if what is None: 
     
    438422 
    439423    def close_folder(self): 
    440         """Close the currently selected folder. 
    441  
    442         @return: Server response. 
     424        """Close the currently selected folder, returning the server 
     425        response string. 
    443426        """ 
    444427        typ, data = self._imap.close() 
     
    446429        return data[0] 
    447430 
    448  
    449431    def create_folder(self, folder): 
    450         """Create a new folder on the server. 
    451  
    452         @param folder: The folder name. 
    453         @return: Server response. 
     432        """Create *folder* on the server returning the server response string. 
    454433        """ 
    455434        typ, data = self._imap.create(self._encode_folder_name(folder)) 
     
    457436        return data[0] 
    458437 
    459  
    460438    def delete_folder(self, folder): 
    461         """Delete a new folder on the server. 
    462  
    463         @param folder: Folder name to delete. 
    464         @return: Server response. 
     439        """Delete *folder* on the server returning the server response string. 
    465440        """ 
    466441        typ, data = self._imap.delete(self._encode_folder_name(folder)) 
     
    468443        return data[0] 
    469444 
    470  
    471445    def folder_exists(self, folder): 
    472         """Determine if a folder exists on the server. 
    473  
    474         @param folder: Full folder name to look for. 
    475         @return: True if the folder exists. False otherwise. 
     446        """Return ``True`` if *folder* exists on the server. 
    476447        """ 
    477448        typ, data = self._imap.list('', self._encode_folder_name(folder)) 
     
    480451        return len(data) == 1 and data[0] != None 
    481452 
    482  
    483453    def subscribe_folder(self, folder): 
    484         """Subscribe to a folder. 
    485  
    486         @param folder: Folder name to subscribe to. 
    487         @return: Server response message. 
     454        """Subscribe to *folder*, returning the server response string. 
    488455        """ 
    489456        typ, data = self._imap.subscribe(self._encode_folder_name(folder)) 
     
    491458        return data 
    492459 
    493  
    494460    def unsubscribe_folder(self, folder): 
    495         """Unsubscribe a folder. 
    496  
    497         @param folder: Folder name to unsubscribe. 
    498         @return: Server response message. 
     461        """Unsubscribe to *folder*, returning the server response string. 
    499462        """ 
    500463        typ, data = self._imap.unsubscribe(self._encode_folder_name(folder)) 
     
    502465        return data 
    503466 
    504  
    505467    def search(self, criteria='ALL', charset=None): 
     468        """Return a list of messages ids matching *criteria*. 
     469 
     470        *criteria* should be a list of of one or more criteria 
     471        specifications or a single critera string. Example values 
     472        include:: 
     473 
     474             'NOT DELETED' 
     475             'UNSEEN' 
     476             'SINCE 1-Feb-2011' 
     477 
     478        *charset* specifies the character set of the strings in the 
     479        criteria. It defaults to US-ASCII. 
     480 
     481        See `RFC-3501 section 6.4.4 <http://tools.ietf.org/html/rfc3501#section-6.4.4>`_ 
     482        for more details. 
     483        """ 
    506484        if not criteria: 
    507485            raise ValueError('no criteria specified') 
     
    527505 
    528506 
    529     def sort(self, sort_criteria, criteria='ALL', charset='UTF-8' ): 
    530         """Returns a list of messages sorted by sort_criteria. 
    531  
    532         Note that this is an extension to the IMAP4: 
    533         http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-19.txt 
     507    def sort(self, sort_criteria, criteria='ALL', charset='UTF-8'): 
     508        """Return a list of message ids sorted by *sort_criteria* and 
     509        optionally filtered by *criteria*. 
     510 
     511        Example values for *sort_criteria* include:: 
     512 
     513            ARRIVAL 
     514            REVERSE SIZE 
     515            SUBJECT 
     516 
     517        The *criteria* argument is as per search().  
     518        See `RFC-5256 <http://tools.ietf.org/html/rfc5256>`_ for full details. 
     519 
     520        Note that SORT is an extension to the IMAP4 standard so it may 
     521        not be supported by all IMAP servers. 
    534522        """ 
    535523        if not criteria: 
     
    553541        self._checkok('sort', typ, data) 
    554542 
    555         return [ long(i) for i in data[0].split() ] 
     543        return [long(i) for i in data[0].split()] 
    556544 
    557545 
    558546    def get_flags(self, messages): 
    559         """Return the flags set for messages 
    560  
    561         @param messages: Message IDs to check flags for 
    562         @return: As for add_f 
    563             { msgid1: [flag1, flag2, ... ], } 
     547        """Return the flags set for each message in *messages*. 
     548 
     549        The return value is a dictionary structured like this: ``{ 
     550        msgid1: [flag1, flag2, ... ], }``. 
    564551        """ 
    565552        response = self.fetch(messages, ['FLAGS']) 
     
    568555 
    569556    def add_flags(self, messages, flags): 
    570         """Add one or more flags to messages 
    571  
    572         @param messages: Message IDs to add flags to 
    573         @param flags: Sequence of flags to add 
    574         @return: The flags set for each message ID as a dictionary 
    575             { msgid1: [flag1, flag2, ... ], } 
     557        """Add *flags* to *messages*. 
     558 
     559        *flags* should be a sequence of strings. 
     560 
     561        Returns the flags set for each modified message (see 
     562        *get_flags*). 
    576563        """ 
    577564        return self._store('+FLAGS', messages, flags) 
     
    579566 
    580567    def remove_flags(self, messages, flags): 
    581         """Remove one or more flags from messages 
    582  
    583         @param messages: Message IDs to remove flags from 
    584         @param flags: Sequence of flags to remove 
    585         @return: As for get_flags. 
     568        """Remove one or more *flags* from *messages*. 
     569 
     570        *flags* should be a sequence of strings. 
     571 
     572        Returns the flags set for each modified message (see 
     573        *get_flags*). 
    586574        """ 
    587575        return self._store('-FLAGS', messages, flags) 
     
    589577 
    590578    def set_flags(self, messages, flags): 
    591         """Set the flags for messages 
    592  
    593         @param messages: Message IDs to set flags for 
    594         @param flags: Sequence of flags to set 
    595         @return: As for get_flags. 
     579        """Set the *flags* for *messages*. 
     580 
     581        *flags* should be a sequence of strings. 
     582 
     583        Returns the flags set for each modified message (see 
     584        *get_flags*). 
    596585        """ 
    597586        return self._store('FLAGS', messages, flags) 
     
    599588 
    600589    def delete_messages(self, messages): 
    601         """Short-hand method for deleting one or more messages 
    602  
    603         @param messages: Message IDs to mark for deletion. 
    604         @return: Same as for get_flags. 
     590        """Delete one or more *messages* from the currently selected 
     591        folder. 
     592 
     593        Returns the flags set for each modified message (see 
     594        *get_flags*). 
    605595        """ 
    606596        return self.add_flags(messages, DELETED) 
    607597 
    608598 
    609     def fetch(self, messages, parts, modifiers=None): 
    610         """Retrieve selected data items for one or more messages. 
    611  
    612         @param messages: Message IDs to fetch. 
    613         @param parts: A sequence of data items to retrieve. 
    614         @param modifiers: An optional sequence of modifiers (where 
    615             supported by the server, eg. ['CHANGEDSINCE 123']). 
    616         @return: A dictionary indexed by message number. Each item is itself a 
    617             dictionary containing the requested message parts. 
    618             INTERNALDATE parts will be returned as datetime objects converted 
    619             to the local machine's time zone. 
     599    def fetch(self, messages, data, modifiers=None): 
     600        """Retrieve selected *data* associated with one or more *messages*. 
     601 
     602        *data* should be specified as a sequnce of strings, one item 
     603        per data selector, for example ``['INTERNALDATE', 
     604        'RFC822']``. 
     605 
     606        *modifiers* are required for some extensions to the IMAP 
     607        protocol (eg. RFC 4551). These should be a sequnce of strings 
     608        if specified, for example ``['CHANGEDSINCE 123']``. 
     609 
     610        A dictionary is returned, indexed by message number. Each item 
     611        in this dictionary is also a dictionary, with an entry 
     612        corresponding to each item in *data*. 
     613 
     614        In addition to an element for each *data* item, the dict 
     615        returned for each message also contains a *SEQ* key containing 
     616        the sequence number for the message. This allows for mapping 
     617        between the UID and sequence number (when the *use_uid* 
     618        property is ``True``). 
     619 
     620        Example:: 
     621 
     622            >> c.fetch([3293, 3230], ['INTERNALDATE', 'FLAGS']) 
     623            {3230: {'FLAGS': ('\\Seen',), 
     624                    'INTERNALDATE': datetime.datetime(2011, 1, 30, 13, 32, 9), 
     625                    'SEQ': 84}, 
     626             3293: {'FLAGS': (), 
     627                    'INTERNALDATE': datetime.datetime(2011, 2, 24, 19, 30, 36), 
     628                    'SEQ': 110}} 
     629 
    620630        """ 
    621631        if not messages: 
     
    623633 
    624634        msg_list = messages_to_str(messages) 
    625         parts_list = seq_to_parenlist([p.upper() for p in parts]) 
     635        data_list = seq_to_parenlist([p.upper() for p in data]) 
    626636        modifiers_list = None 
    627637        if modifiers is not None: 
     
    629639 
    630640        if self.use_uid: 
    631             tag = self._imap._command('UID', 'FETCH', msg_list, parts_list, modifiers_list) 
     641            tag = self._imap._command('UID', 'FETCH', msg_list, data_list, modifiers_list) 
    632642        else: 
    633             tag = self._imap._command('FETCH', msg_list, parts_list, modifiers_list) 
     643            tag = self._imap._command('FETCH', msg_list, data_list, modifiers_list) 
    634644        typ, data = self._imap._command_complete('FETCH', tag) 
    635645        self._checkok('fetch', typ, data) 
     
    637647        return parse_fetch_response(data) 
    638648 
    639  
    640649    def append(self, folder, msg, flags=(), msg_time=None): 
    641         """Append a message to a folder 
    642  
    643         @param folder: Folder name to append to. 
    644         @param msg: Message body as a string. 
    645         @param flags: Sequnce of message flags to set. If not specified no 
    646             flags will be set. 
    647         @param msg_time: Optional date and time to set for the message. The 
    648             server will set a time if it isn't specified. If msg_time contains 
    649             timezone information (tzinfo), this will be honoured. Otherwise the 
    650             local machine's time zone sent to the server. 
    651         @type msg_time: datetime.datetime 
    652         @return: The append response returned by the server. 
    653         @rtype: str 
     650        """Append a message to *folder*. 
     651 
     652        *msg* should be a string contains the full message including 
     653        headers. 
     654 
     655        *flags* should be a sequence of message flags to set. If not 
     656        specified no flags will be set. 
     657 
     658        *msg_time* is an optional datetime instance specifying the 
     659        date and time to set on the message. The server will set a 
     660        time if it isn't specified. If *msg_time* contains timezone 
     661        information (tzinfo), this will be honoured. Otherwise the 
     662        local machine's time zone sent to the server. 
     663 
     664        Returns the APPEND response as returned by the server. 
    654665        """ 
    655666        if msg_time: 
     
    666677        return data[0] 
    667678 
    668  
    669679    def copy(self, messages, folder): 
    670         """Copy one or more messages from the current folder to another folder 
    671  
    672         @param messages: Message IDs to fetch. 
    673         @param folder: Folder name to append to. 
    674         @return: The COPY command response message returned by the 
    675           server. 
     680        """Copy one or more messages from the current folder to 
     681        *folder*. Returns the COPY response string returned by the 
     682        server. 
    676683        """ 
    677684        msg_list = messages_to_str(messages) 
     
    685692        return data[0] 
    686693 
    687  
    688694    def expunge(self): 
     695        """Remove any messages from the currently selected folder that 
     696        have the ``\\Deleted`` flag set. 
     697        """ 
    689698        typ, data = self._imap.expunge() 
    690699        self._checkok('expunge', typ, data) 
    691700        #TODO: expunge response 
    692701 
    693  
    694702    def getacl(self, folder): 
    695         """Get the ACL for a folder 
    696  
    697         @param folder: Folder name to get the ACL for. 
    698         @return: A list of (who, acl) tuples 
     703        """Returns a list of ``(who, acl)`` tuples describing the 
     704        access controls for *folder*. 
    699705        """ 
    700706        typ, data = self._imap.getacl(folder) 
     
    705711        return [(parts[i], parts[i+1]) for i in xrange(0, len(parts), 2)] 
    706712 
    707  
    708713    def setacl(self, folder, who, what): 
    709         """Set an ACL for a folder 
    710  
    711         @param folder: Folder name to set an ACL for. 
    712         @param who: User or group ID for the ACL. 
    713         @param what: A string describing the ACL. Set to '' to remove an ACL. 
    714         @return: Server response string. 
     714        """Set an ACL (*what*) for user (*who*) for a folder. 
     715 
     716        Set *what* to an empty string to remove an ACL. Returns the 
     717        server response string. 
    715718        """ 
    716719        typ, data = self._imap.setacl(folder, who, what) 
     
    718721        return data[0] 
    719722 
    720  
    721723    def _check_resp(self, expected, command, typ, data): 
    722724        """Check command responses for errors. 
    723725 
    724         @raise: Error if a command failed. 
     726        Raises IMAPClient.Error if the command fails. 
    725727        """ 
    726728        if typ != expected: 
    727729            raise self.Error('%s failed: %r' % (command, data[0])) 
    728730 
    729  
    730731    def _checkok(self, command, typ, data): 
    731732        self._check_resp('OK', command, typ, data) 
    732733 
    733  
    734734    def _checkbye(self, command, typ, data): 
    735735        self._check_resp('BYE', command, typ, data) 
    736736 
    737  
    738737    def _store(self, cmd, messages, flags): 
    739         """Worker function for flag manipulation functions 
    740  
    741         @param cmd: STORE command to use (eg. '+FLAGS') 
    742         @param messages: Sequence of message IDs 
    743         @param flags: Sequence of flags to set. 
    744         @return: The flags set for each message ID as a dictionary 
    745             { msgid1: [flag1, flag2, ... ], } 
     738        """Worker function for the various flag manipulation methods. 
     739 
     740        *cmd* is the STORE command to use (eg. '+FLAGS'). 
    746741        """ 
    747742        if not messages: 
     
    758753        return self._flatten_dict(parse_fetch_response((data))) 
    759754 
    760  
    761755    def _flatten_dict(self, fetch_dict): 
    762756        return dict([ 
     
    769763            return imap_utf7.decode(name) 
    770764        return name 
    771  
    772765 
    773766    def _encode_folder_name(self, name): 
     
    806799 
    807800def messages_to_str(messages): 
    808     """Convert a sequence of messages ids or a single message id into an 
    809     message ID list for use with IMAP commands. 
    810  
    811     @param messages: A sequence of messages IDs or a single message ID. 
    812         (eg. [1,4,5,7,8]) 
    813     @return: Message list string (eg. "1,4,5,6,8") 
     801    """Convert a sequence of messages ids or a single integer message id 
     802    into an id list string for use with IMAP commands 
    814803    """ 
    815804    if isinstance(messages, (str, int, long)): 
     
    821810 
    822811def seq_to_parenlist(flags): 
    823     """Convert a sequence into parenthised list for use with IMAP commands 
    824  
    825     @param flags: Sequence to process (eg. ['abc', 'def']) 
    826     @return: IMAP parenthenised list (eg. '(abc def)') 
     812    """Convert a sequence of strings into parenthised list string for 
     813    use with IMAP commands. 
    827814    """ 
    828815    if isinstance(flags, str): 
     
    834821 
    835822def datetime_to_imap(dt): 
    836     """Convert a datetime instance to a IMAP datetime string 
    837  
    838     If timezone information is missing the current system timezone is used. 
     823    """Convert a datetime instance to a IMAP datetime string. 
     824 
     825    If timezone information is missing the current system 
     826    timezone is used. 
    839827    """ 
    840828    if not dt.tzinfo: 
  • imapclient/imapclient.py

    r248 r249  
    44 
    55import re 
     6import select 
     7import socket 
     8import sys 
     9import warnings 
     10from datetime import datetime 
     11from operator import itemgetter 
     12 
    613import imaplib 
    714import response_lexer 
    8 from operator import itemgetter 
    9 import warnings 
    10 #imaplib.Debug = 5 
     15 
     16     
     17try: 
     18    import oauth2 
     19except ImportError: 
     20    oauth2 = None 
    1121 
    1222import imap_utf7 
     
    1424 
    1525 
    16 __all__ = ['IMAPClient', 'DELETED', 'SEEN', 'ANSWERED', 'FLAGGED', 'DRAFT', 
    17     'RECENT'] 
     26__all__ = ['IMAPClient', 'DELETED', 'SEEN', 'ANSWERED', 'FLAGGED', 'DRAFT', 'RECENT'] 
    1827 
    1928from response_parser import parse_response, parse_fetch_response 
     
    2231if 'XLIST' not in imaplib.Commands: 
    2332  imaplib.Commands['XLIST'] = imaplib.Commands['LIST'] 
     33 
     34# ...and IDLE 
     35if 'IDLE' not in imaplib.Commands: 
     36  imaplib.Commands['IDLE'] = imaplib.Commands['APPEND'] 
    2437 
    2538 
     
    6376 
    6477    def __init__(self, host, port=None, use_uid=True, ssl=False): 
    65         if ssl: 
    66             ImapClass = imaplib.IMAP4_SSL 
    67             default_port = 993 
    68         else: 
    69             ImapClass = imaplib.IMAP4 
    70             default_port = 143 
    71  
    7278        if port is None: 
    73             port = default_port 
    74  
    75         self._imap = ImapClass(host, port) 
     79            port = ssl and 993 or 143 
     80 
     81        self.host = host 
     82        self.port = port 
     83        self.ssl = ssl 
    7684        self.use_uid = use_uid 
    7785        self.folder_encode = True 
    78  
    79     
     86        self.log_file = sys.stderr 
     87 
     88        self._imap = self._create_IMAP4() 
     89        self._imap._mesg = self._log    # patch in custom debug log method 
     90        self._idle_tag = None 
     91 
     92    def _create_IMAP4(self): 
     93        # Create the IMAP instance in a separate method to make unit tests easier 
     94        ImapClass = self.ssl and imaplib.IMAP4_SSL or imaplib.IMAP4 
     95        return ImapClass(self.host, self.port) 
     96 
    8097    def login(self, username, password): 
    8198        """Login using *username* and *password*, returning the 
     
    86103        return data[0] 
    87104 
     105    def oauth_login(self, url, oauth_token, oauth_token_secret, 
     106                    consumer_key='anonymous', consumer_secret='anonymous'): 
     107        """Authenticate using oauth. 
     108 
     109        @param url: The OAuth request URL. 
     110        @param oauth_token: An OAuth key. 
     111        @param oauth_token_secret: An OAuth secret. 
     112        @param consumer_key: An OAuth consumer key (defaults to 'anonymous'). 
     113        @param consumer_secret: An OAuth consumer secret (defaults to 'anonymous'). 
     114        """ 
     115        if oauth2: 
     116            token = oauth2.Token(oauth_token, oauth_token_secret) 
     117            consumer = oauth2.Consumer(consumer_key, consumer_secret) 
     118            xoauth_callable = lambda x: oauth2.build_xoauth_string(url, consumer, token) 
     119             
     120            typ, data = self._imap.authenticate('XOAUTH', xoauth_callable) 
     121            self._checkok('authenticate', typ, data) 
     122            return data[0] 
     123        else: 
     124            raise self.Error('The optional oauth2 dependency is needed for oauth authentication') 
    88125 
    89126    def logout(self): 
     
    248285        self._checkok('select', typ, data) 
    249286        return self._process_select_response(self._imap.untagged_responses) 
    250  
    251287 
    252288    def _process_select_response(self, resp): 
     
    266302        return out 
    267303 
     304    def idle(self): 
     305        """Put server into IDLE mode. 
     306 
     307        In this mode the server will return unsolicited responses 
     308        about changes to the selected mailbox. 
     309 
     310        This method returns immediately. Use idle_check() to check for 
     311        IDLE responses and idle_done() to stop IDLE mode. 
     312 
     313        Note: Any other commmands issued while the server is in IDLE 
     314        mode will fail. 
     315 
     316        See RFC 2177 for more information about the IDLE extension. 
     317        http://tools.ietf.org/html/rfc2177 
     318        """ 
     319        self._idle_tag = self._imap._command('IDLE') 
     320        resp = self._imap._get_response() 
     321        if resp is not None: 
     322            raise self.Error('Unexpected IDLE response: %s' % resp) 
     323 
     324    def idle_check(self, timeout=None): 
     325        """Check for any IDLE responses sent by the server. 
     326 
     327        This method should only be called if the server is in IDLE 
     328        mode (see idle()). 
     329 
     330        By default, this method will block until an IDLE response is 
     331        received. If timeout is provided, the call will block for at 
     332        most this number of seconds while waiting for an IDLE response. 
     333 
     334        The return value is a list of received IDLE responses. These 
     335        will be parsed with values converted to appropriate types. For 
     336        example: 
     337 
     338        [('OK', 'Still here'), 
     339         (1, 'EXISTS'), 
     340         (1, 'FETCH', ('FLAGS', ('\\NotJunk',)))] 
     341        """ 
     342        # make the socket non-blocking so the timeout can be 
     343        # implemented for this call 
     344        if self.ssl: 
     345            sock = self._imap.sslobj 
     346        else: 
     347            sock = self._imap.sock 
     348        sock.setblocking(0) 
     349        try: 
     350            resps = [] 
     351            rs, _, _ = select.select([sock], [], [], timeout) 
     352            if rs: 
     353                while True: 
     354                    try: 
     355                        line = self._imap._get_line() 
     356                    except (socket.timeout, socket.error): 
     357                        break 
     358                    else: 
     359                        resps.append(_parse_idle_response(line)) 
     360            return resps 
     361        finally: 
     362            sock.setblocking(1) 
     363 
     364    def idle_done(self): 
     365        """Take the server out of IDLE mode. 
     366 
     367        This method should only be called if the server is in IDLE mode. 
     368 
     369        The return value is (command_text, idle_responses) where 
     370        command_text is the text sent by the server when the IDLE 
     371        command finished (eg. 'Idle terminated') and idle_responses is 
     372        a list of parsed idle responses received since the last call 
     373        to idle_check() (if any). These are returned in parsed form as 
     374        per idle_check(). 
     375        """ 
     376        self._imap.send('DONE\r\n') 
     377        # Slurp up any remaining IDLE responses until the IDLE is done 
     378        tag = self._idle_tag 
     379        tagged_commands = self._imap.tagged_commands 
     380        resps = [] 
     381        while True: 
     382            line = self._imap._get_response() 
     383            if tagged_commands[tag]: 
     384                break 
     385            resps.append(_parse_idle_response(line)) 
     386        self._idle_tag = None 
     387        typ, data = tagged_commands.pop(tag) 
     388        self._checkok('idle', typ, data) 
     389        return data[0], resps 
    268390 
    269391    def folder_status(self, folder, what=None): 
     
    585707        self._checkok('getacl', typ, data) 
    586708 
    587         parts = list(response_lexer.Lexer([data[0]])) 
     709        parts = list(response_lexer.TokenSource(data)) 
    588710        parts = parts[1:]       # First item is folder name 
    589  
    590         out = [] 
    591         for i in xrange(0, len(parts), 2): 
    592             out.append((parts[i], parts[i+1])) 
    593         return out 
     711        return [(parts[i], parts[i+1]) for i in xrange(0, len(parts), 2)] 
    594712 
    595713    def setacl(self, folder, who, what): 
     
    660778        return _quote_arg(name) 
    661779 
     780    def __debug_get(self): 
     781        return self._imap.debug 
     782 
     783    def __debug_set(self, level): 
     784        if level is True: 
     785            level = 4 
     786        elif level is False: 
     787            level = 0 
     788        self._imap.debug = level 
     789 
     790    debug = property(__debug_get, __debug_set, 
     791                     doc="Set debug level. Integers from 0 to 5 or, True and " \ 
     792                         "False can be used. True specifies debug level 4. " \ 
     793                         "Debug output goes to stderr.") 
     794 
     795    def _log(self, text): 
     796        self.log_file.write('%s %s\n' % (datetime.now().strftime('%M:%S.%f'), text)) 
     797        self.log_file.flush() 
     798         
    662799 
    663800def messages_to_str(messages): 
     
    698835  arg = arg.replace('"', '\\"') 
    699836  return '"%s"' % arg 
     837 
     838 
     839def _parse_idle_response(text): 
     840    assert text.startswith('* ') 
     841    text = text[2:] 
     842    if text.startswith(('OK ', 'NO ')): 
     843        return tuple(text.split(' ', 1)) 
     844    return parse_response([text])  
     845