Changeset 249:456de0713e01
- Timestamp:
- 05/06/11 17:24:20 (12 months ago)
- 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. - Files:
-
- 2 modified
-
imapclient/imapclient.py (modified) (37 diffs)
-
imapclient/imapclient.py (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
imapclient/imapclient.py
r226 r249 56 56 class IMAPClient(object): 57 57 """ 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``). 89 68 """ 90 69 … … 97 76 98 77 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 """106 78 if port is None: 107 79 port = ssl and 993 or 143 … … 124 96 125 97 def login(self, username, password): 126 """Perform a simple login 98 """Login using *username* and *password*, returning the 99 server response. 127 100 """ 128 101 typ, data = self._imap.login(username, password) … … 152 125 153 126 def logout(self): 154 """ Perform a logout127 """Logout, returning the server response. 155 128 """ 156 129 typ, data = self._imap.logout() … … 160 133 161 134 def capabilities(self): 162 """Returns the server capability list 135 """Returns the server capability list. 163 136 """ 164 137 return self._imap.capabilities … … 166 139 167 140 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*. 171 142 """ 172 143 # FIXME: this will not detect capabilities that are backwards … … 181 152 182 153 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. 184 156 185 157 Each element may be None if no namespace of that type exists, … … 187 159 188 160 For convenience the tuple elements may be accessed 189 positionally or attributes named "personal", "other"and190 "shared".161 positionally or using attributes named *personal*, *other* and 162 *shared*. 191 163 192 164 See RFC 2342 for more details. … … 197 169 198 170 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(). 207 178 """ 208 179 warnings.warn(DeprecationWarning('get_folder_delimiter is going away. Use namespace() instead.')) … … 213 184 214 185 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. 228 201 """ 229 202 return self._do_list('LIST', directory, pattern) 230 203 231 204 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(). 246 233 """ 247 234 return self._do_list('XLIST', directory, pattern) 248 235 249 236 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(). 260 242 """ 261 243 return self._do_list('LSUB', directory, pattern) … … 283 265 284 266 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 292 276 {'EXISTS': 3, 293 277 'FLAGS': ('\\Answered', '\\Flagged', '\\Deleted', ... ), … … 406 390 407 391 def folder_status(self, folder, what=None): 408 """Re quests the status from folder.409 410 @param folder: The folder name.411 @param what: A sequence of status items to query. Defaults to412 ('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: dict392 """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*. 416 400 """ 417 401 if what is None: … … 438 422 439 423 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. 443 426 """ 444 427 typ, data = self._imap.close() … … 446 429 return data[0] 447 430 448 449 431 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. 454 433 """ 455 434 typ, data = self._imap.create(self._encode_folder_name(folder)) … … 457 436 return data[0] 458 437 459 460 438 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. 465 440 """ 466 441 typ, data = self._imap.delete(self._encode_folder_name(folder)) … … 468 443 return data[0] 469 444 470 471 445 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. 476 447 """ 477 448 typ, data = self._imap.list('', self._encode_folder_name(folder)) … … 480 451 return len(data) == 1 and data[0] != None 481 452 482 483 453 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. 488 455 """ 489 456 typ, data = self._imap.subscribe(self._encode_folder_name(folder)) … … 491 458 return data 492 459 493 494 460 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. 499 462 """ 500 463 typ, data = self._imap.unsubscribe(self._encode_folder_name(folder)) … … 502 465 return data 503 466 504 505 467 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 """ 506 484 if not criteria: 507 485 raise ValueError('no criteria specified') … … 527 505 528 506 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. 534 522 """ 535 523 if not criteria: … … 553 541 self._checkok('sort', typ, data) 554 542 555 return [ long(i) for i in data[0].split()]543 return [long(i) for i in data[0].split()] 556 544 557 545 558 546 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, ... ], }``. 564 551 """ 565 552 response = self.fetch(messages, ['FLAGS']) … … 568 555 569 556 def add_flags(self, messages, flags): 570 """Add one or more flags to messages571 572 @param messages: Message IDs to add flags to573 @param flags: Sequence of flags to add 574 @return: The flags set for each message ID as a dictionary575 { 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*). 576 563 """ 577 564 return self._store('+FLAGS', messages, flags) … … 579 566 580 567 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*). 586 574 """ 587 575 return self._store('-FLAGS', messages, flags) … … 589 577 590 578 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*). 596 585 """ 597 586 return self._store('FLAGS', messages, flags) … … 599 588 600 589 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*). 605 595 """ 606 596 return self.add_flags(messages, DELETED) 607 597 608 598 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 620 630 """ 621 631 if not messages: … … 623 633 624 634 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]) 626 636 modifiers_list = None 627 637 if modifiers is not None: … … 629 639 630 640 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) 632 642 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) 634 644 typ, data = self._imap._command_complete('FETCH', tag) 635 645 self._checkok('fetch', typ, data) … … 637 647 return parse_fetch_response(data) 638 648 639 640 649 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. 654 665 """ 655 666 if msg_time: … … 666 677 return data[0] 667 678 668 669 679 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. 676 683 """ 677 684 msg_list = messages_to_str(messages) … … 685 692 return data[0] 686 693 687 688 694 def expunge(self): 695 """Remove any messages from the currently selected folder that 696 have the ``\\Deleted`` flag set. 697 """ 689 698 typ, data = self._imap.expunge() 690 699 self._checkok('expunge', typ, data) 691 700 #TODO: expunge response 692 701 693 694 702 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*. 699 705 """ 700 706 typ, data = self._imap.getacl(folder) … … 705 711 return [(parts[i], parts[i+1]) for i in xrange(0, len(parts), 2)] 706 712 707 708 713 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. 715 718 """ 716 719 typ, data = self._imap.setacl(folder, who, what) … … 718 721 return data[0] 719 722 720 721 723 def _check_resp(self, expected, command, typ, data): 722 724 """Check command responses for errors. 723 725 724 @raise: Error if a command failed.726 Raises IMAPClient.Error if the command fails. 725 727 """ 726 728 if typ != expected: 727 729 raise self.Error('%s failed: %r' % (command, data[0])) 728 730 729 730 731 def _checkok(self, command, typ, data): 731 732 self._check_resp('OK', command, typ, data) 732 733 733 734 734 def _checkbye(self, command, typ, data): 735 735 self._check_resp('BYE', command, typ, data) 736 736 737 738 737 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'). 746 741 """ 747 742 if not messages: … … 758 753 return self._flatten_dict(parse_fetch_response((data))) 759 754 760 761 755 def _flatten_dict(self, fetch_dict): 762 756 return dict([ … … 769 763 return imap_utf7.decode(name) 770 764 return name 771 772 765 773 766 def _encode_folder_name(self, name): … … 806 799 807 800 def 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 814 803 """ 815 804 if isinstance(messages, (str, int, long)): … … 821 810 822 811 def 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. 827 814 """ 828 815 if isinstance(flags, str): … … 834 821 835 822 def 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. 839 827 """ 840 828 if not dt.tzinfo: -
imapclient/imapclient.py
r248 r249 4 4 5 5 import re 6 import select 7 import socket 8 import sys 9 import warnings 10 from datetime import datetime 11 from operator import itemgetter 12 6 13 import imaplib 7 14 import response_lexer 8 from operator import itemgetter 9 import warnings 10 #imaplib.Debug = 5 15 16 17 try: 18 import oauth2 19 except ImportError: 20 oauth2 = None 11 21 12 22 import imap_utf7 … … 14 24 15 25 16 __all__ = ['IMAPClient', 'DELETED', 'SEEN', 'ANSWERED', 'FLAGGED', 'DRAFT', 17 'RECENT'] 26 __all__ = ['IMAPClient', 'DELETED', 'SEEN', 'ANSWERED', 'FLAGGED', 'DRAFT', 'RECENT'] 18 27 19 28 from response_parser import parse_response, parse_fetch_response … … 22 31 if 'XLIST' not in imaplib.Commands: 23 32 imaplib.Commands['XLIST'] = imaplib.Commands['LIST'] 33 34 # ...and IDLE 35 if 'IDLE' not in imaplib.Commands: 36 imaplib.Commands['IDLE'] = imaplib.Commands['APPEND'] 24 37 25 38 … … 63 76 64 77 def __init__(self, host, port=None, use_uid=True, ssl=False): 65 if ssl:66 ImapClass = imaplib.IMAP4_SSL67 default_port = 99368 else:69 ImapClass = imaplib.IMAP470 default_port = 14371 72 78 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 76 84 self.use_uid = use_uid 77 85 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 80 97 def login(self, username, password): 81 98 """Login using *username* and *password*, returning the … … 86 103 return data[0] 87 104 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') 88 125 89 126 def logout(self): … … 248 285 self._checkok('select', typ, data) 249 286 return self._process_select_response(self._imap.untagged_responses) 250 251 287 252 288 def _process_select_response(self, resp): … … 266 302 return out 267 303 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 268 390 269 391 def folder_status(self, folder, what=None): … … 585 707 self._checkok('getacl', typ, data) 586 708 587 parts = list(response_lexer. Lexer([data[0]]))709 parts = list(response_lexer.TokenSource(data)) 588 710 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)] 594 712 595 713 def setacl(self, folder, who, what): … … 660 778 return _quote_arg(name) 661 779 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 662 799 663 800 def messages_to_str(messages): … … 698 835 arg = arg.replace('"', '\\"') 699 836 return '"%s"' % arg 837 838 839 def _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
