Changeset 243:35fc2ded237d
- Timestamp:
- 19/02/11 23:37:20 (15 months ago)
- Branch:
- default
- Parents:
- 240:ca209b0b5f0c (diff), 242:295f2c9d5b4f (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:
-
- 1 removed
- 2 modified
-
imapclient/imapclient.py (modified) (8 diffs)
-
imapclient/imapclient.py (modified) (31 diffs)
-
run_tests.py (deleted)
Legend:
- Unmodified
- Added
- Removed
-
imapclient/imapclient.py
r240 r243 1 # Copyright (c) 201 0, Menno Smits1 # Copyright (c) 2011, Menno Smits 2 2 # Released subject to the New BSD License 3 3 # Please see http://en.wikipedia.org/wiki/BSD_licenses … … 6 6 import imaplib 7 7 import response_lexer 8 from operator import itemgetter 9 import warnings 8 10 #imaplib.Debug = 5 9 11 … … 30 32 RECENT = r'\Recent' # This flag is read-only 31 33 34 class Namespace(tuple): 35 def __new__(cls, personal, other, shared): 36 return tuple.__new__(cls, (personal, other, shared)) 37 38 personal = property(itemgetter(0)) 39 other = property(itemgetter(1)) 40 shared = property(itemgetter(2)) 41 32 42 33 43 class IMAPClient(object): … … 49 59 ReadOnlyError = imaplib.IMAP4.readonly 50 60 51 re_sep = re.compile('^\(\("[^"]*" "([^"]+)"\)\)')52 61 re_status = re.compile(r'^\s*"?(?P<folder>[^"]+)"?\s+' 53 62 r'\((?P<status_items>.*)\)$') … … 105 114 return False 106 115 107 def get_folder_delimiter(self): 108 """Return the folder separator used by the IMAP server (eg. "/"). 116 def namespace(self): 117 """Return the namespace for the account as a (personal, other, shared) tuple. 118 119 Each element may be None if no namespace of that type exists, 120 or a sequence of (prefix, separator) pairs. 121 122 For convenience the tuple elements may be accessed 123 positionally or attributes named "personal", "other" and 124 "shared". 125 126 See RFC 2342 for more details. 109 127 """ 110 128 typ, data = self._imap.namespace() 111 129 self._checkok('namespace', typ, data) 112 113 match = self.re_sep.match(data[0]) 114 if match: 115 return match.group(1) 116 else: 117 raise self.Error('could not determine folder separator') 130 return Namespace(*parse_response(data)) 131 132 def get_folder_delimiter(self): 133 """Determine the folder separator used by the IMAP server. 134 135 WARNING: The implementation just picks the first folder 136 separator from the first namespace returned. This is not 137 particularly sensible. Use namespace instead(). 138 139 @return: The folder separator. 140 @rtype: string 141 """ 142 warnings.warn(DeprecationWarning('get_folder_delimiter is going away. Use namespace() instead.')) 143 for part in self.namespace(): 144 for ns in part: 145 return ns[1] 146 raise self.Error('could not determine folder separator') 118 147 119 148 def list_folders(self, directory="", pattern="*"): … … 415 444 416 445 417 def fetch(self, messages, parts ):446 def fetch(self, messages, parts, modifiers=None): 418 447 """Retrieve selected data items for one or more messages. 419 448 420 449 @param messages: Message IDs to fetch. 421 450 @param parts: A sequence of data items to retrieve. 451 @param modifiers: An optional sequence of modifiers (where 452 supported by the server, eg. ['CHANGEDSINCE 123']). 422 453 @return: A dictionary indexed by message number. Each item is itself a 423 454 dictionary containing the requested message parts. … … 430 461 msg_list = messages_to_str(messages) 431 462 parts_list = seq_to_parenlist([p.upper() for p in parts]) 463 modifiers_list = None 464 if modifiers is not None: 465 modifiers_list = seq_to_parenlist([m.upper() for m in modifiers]) 432 466 433 467 if self.use_uid: 434 tag = self._imap._command('UID', 'FETCH', msg_list, parts_list )435 else: 436 tag = self._imap._command('FETCH', msg_list, parts_list )468 tag = self._imap._command('UID', 'FETCH', msg_list, parts_list, modifiers_list) 469 else: 470 tag = self._imap._command('FETCH', msg_list, parts_list, modifiers_list) 437 471 typ, data = self._imap._command_complete('FETCH', tag) 438 472 self._checkok('fetch', typ, data) 439 473 typ, data = self._imap._untagged_response(typ, data, 'FETCH') 440 # appears to be a special case - no 'untagged' responses (ie, no441 # folders) results in [None]442 if data == [None]:443 return {}444 445 474 return parse_fetch_response(data) 446 475 … … 555 584 typ, data = self._imap.store(msg_list, cmd, flag_list) 556 585 self._checkok('store', typ, data) 557 558 586 return self._flatten_dict(parse_fetch_response((data))) 559 587 -
imapclient/imapclient.py
r204 r243 43 43 class IMAPClient(object): 44 44 """ 45 A Pythonic, easy-to-use IMAP client class. 46 47 Unlike imaplib, arguments and returns values are Pythonic and readily 48 usable. Exceptions are raised when problems occur (no error checking of 49 return values is required). 50 51 Message unique identifiers (UID) can be used with any call. The use_uid 52 argument to the constructor and the use_uid attribute control whether UIDs 53 are used. 54 55 Any method that accepts message id's takes either a sequence containing 56 message IDs (eg. [1,2,3]) or a single message ID as an integer. 57 58 Any method that accepts message flags takes either a sequence containing 59 message flags (eg. [DELETED, 'foo', 'Bar']) or a single message flag (eg. 60 'Foo'). See the constants at the top of this file for commonly used flags. 61 62 Any method that takes a folder name will accept a standard string or a 63 unicode string. Unicode strings will be transparently encoded using 64 modified UTF-7 as specified by RFC-2060. Such folder names will be returned 65 as unicode strings by methods that return folder names. 66 67 Transparent folder name encoding can be enabled or disabled with the 68 folder_encode attribute. It defaults to True. 69 70 The IMAP related exceptions that will be raised by this class are: 71 IMAPClient.Error 72 IMAPClient.AbortError 73 IMAPClient.ReadOnlyError 74 These are aliases for the imaplib.IMAP4 exceptions of the same name. Socket 75 errors may also be raised in the case of network errors. 45 A connection to the IMAP server specified by *host* is made when 46 the class is instantiated. 47 48 *port* defaults to 143, or 993 if *ssl* is ``True``. 49 50 If *use_uid* is ``True`` unique message UIDs be used for all calls 51 that accept message ids (defaults to ``True``). 52 53 If *ssl* is ``True`` an SSL connection will be made (defaults to 54 ``False``). 76 55 """ 77 56 … … 84 63 85 64 def __init__(self, host, port=None, use_uid=True, ssl=False): 86 """Initialise object instance and connect to the remote IMAP server.87 88 @param host: The IMAP server address/hostname to connect to.89 @param port: The port number to use (default is 143, 993 for SSL).90 @param use_uid: Should message UIDs be used (default is True).91 @param ssl: Make an SSL connection (default is False)92 """93 65 if ssl: 94 66 ImapClass = imaplib.IMAP4_SSL … … 107 79 108 80 def login(self, username, password): 109 """Perform a simple login 81 """Login using *username* and *password*, returning the 82 server response. 110 83 """ 111 84 typ, data = self._imap.login(username, password) … … 115 88 116 89 def logout(self): 117 """ Perform a logout90 """Logout, returning the server response. 118 91 """ 119 92 typ, data = self._imap.logout() … … 123 96 124 97 def capabilities(self): 125 """Returns the server capability list 98 """Returns the server capability list. 126 99 """ 127 100 return self._imap.capabilities … … 129 102 130 103 def has_capability(self, capability): 131 """Checks if the server has the given capability. 132 133 @param capability: capability to test (eg 'SORT') 104 """Return ``True`` if the IMAP server has the given *capability*. 134 105 """ 135 106 # FIXME: this will not detect capabilities that are backwards … … 176 147 177 148 def list_folders(self, directory="", pattern="*"): 178 """Get a listing of folders on the server. 179 180 The default behaviour (no args) will list all folders for the logged in 181 user. 182 183 @param directory: The base directory to look for folders from. 184 @param pattern: A pattern to match against folder names. Only folder 185 names matching this pattern will be returned. Wildcards accepted. 186 @return: A list of (flags, delim, folder_name). Each folder name will 187 be either a string or a unicode string (if the folder on the 188 server required decoding). If the folder_encode attribute is 189 False, no decoding will be performed and only ordinary strings 190 will be returned. 149 """Get a listing of folders on the server as a list of 150 ``(flags, delimiter, name)`` tuples. 151 152 Calling list_folders with no arguments will list all 153 folders. 154 155 Specifying *directory* will limit returned folders to that 156 base directory. Specifying *pattern* will limit returned 157 folders to those with matching names. Wildcards (``*`` and 158 ``?``) are supported in *pattern*. 159 160 #XXX check wildcard facts 161 162 Each folder name will be either a string or a unicode string 163 (if the folder on the server required decoding). If the 164 folder_encode attribute is False, no decoding will be 165 performed and only ordinary strings will be returned. 166 167 #XXX check the above is still true 191 168 """ 192 169 return self._do_list('LIST', directory, pattern) 193 170 194 171 def xlist_folders(self, directory="", pattern="*"): 195 """A gmail-specific IMAP extension. 196 197 This method returns special flags for each folder and a localized name 198 for certain folders (eg, the name of the 'inbox' may be localized as the 199 flags can be used to determine the actual inbox, even if the name has 200 been localized. It is the responsibility of the caller to either check 201 for 'XLIST' in the server capabilites, or to handle the error if the 202 server doesn't support this externsion. 203 204 @param directory: The base directory to look for folders from. 205 @param pattern: A pattern to match against folder names. Only folder 206 names matching this pattern will be returned. Wildcards accepted. 207 @return: A list of (flags, delim, folder_name). As per the return of 208 list_folders(). 172 """Execute the XLIST command, returning``(flags, delimiter, 173 name)`` tuples. 174 175 This method returns special flags for each folder and a 176 localized name for certain folders (e.g. the name of the 177 'inbox' may be localized and the flags can be used to 178 determine the actual inbox, even if the name has been 179 localized. 180 181 XXX example response 182 183 This is a Gmail-specific IMAP extension. It is the 184 responsibility of the caller to either check for 'XLIST' in 185 the server capabilites, or to handle the error if the server 186 doesn't support this externsion. 187 188 The *directory* and *pattern* arguments are as per 189 list_folders(). 209 190 """ 210 191 return self._do_list('XLIST', directory, pattern) 211 192 212 193 def list_sub_folders(self, directory="", pattern="*"): 213 """Get a listing of subscribed folders on the server. 214 215 The default behaviour (no args) will list all subscribed folders for the 216 logged in user. 217 218 @param directory: The base directory to look for folders from. 219 @param pattern: A pattern to match against folder names. Only folder 220 names matching this pattern will be returned. Wildcards accepted. 221 @return: A list of (flags, delim, folder_name). As per the return of 222 list_folders(). 194 """Return a list of subscribed folders on the server as 195 ``(flags, delimiter, name)`` tuples. 196 197 The default behaviour will list all subscribed folders. The 198 *directory* and *pattern* arguments are as per list_folders(). 223 199 """ 224 200 return self._do_list('LSUB', directory, pattern) … … 284 260 285 261 def folder_status(self, folder, what=None): 286 """Re quests the status from folder.287 288 @param folder: The folder name.289 @param what: A sequence of status items to query. Defaults to290 ('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN').291 @return: Dictionary of the status items for the folder. The keys match 292 the items specified in the what parameter.293 @rtype: dict262 """Return the status of *folder*. 263 264 *what* should be a sequence of status items to query. This 265 defaults to ``('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 266 'UNSEEN')``. 267 268 Returns a dictionary of the status items for the folder with 269 keys matching *what*. 294 270 """ 295 271 if what is None: … … 316 292 317 293 def close_folder(self): 318 """Close the currently selected folder. 319 320 @return: Server response. 294 """Close the currently selected folder, returning the server 295 response string. 321 296 """ 322 297 typ, data = self._imap.close() … … 324 299 return data[0] 325 300 326 327 301 def create_folder(self, folder): 328 """Create a new folder on the server. 329 330 @param folder: The folder name. 331 @return: Server response. 302 """Create *folder* on the server returning the server response string. 332 303 """ 333 304 typ, data = self._imap.create(self._encode_folder_name(folder)) … … 335 306 return data[0] 336 307 337 338 308 def delete_folder(self, folder): 339 """Delete a new folder on the server. 340 341 @param folder: Folder name to delete. 342 @return: Server response. 309 """Delete *folder* on the server returning the server response string. 343 310 """ 344 311 typ, data = self._imap.delete(self._encode_folder_name(folder)) … … 346 313 return data[0] 347 314 348 349 315 def folder_exists(self, folder): 350 """Determine if a folder exists on the server. 351 352 @param folder: Full folder name to look for. 353 @return: True if the folder exists. False otherwise. 316 """Return True if *folder* exists on the server. 354 317 """ 355 318 typ, data = self._imap.list('', self._encode_folder_name(folder)) … … 358 321 return len(data) == 1 and data[0] != None 359 322 360 361 323 def subscribe_folder(self, folder): 362 """Subscribe to a folder. 363 364 @param folder: Folder name to subscribe to. 365 @return: Server response message. 324 """Subscribe to *folder*, returning the server response string. 366 325 """ 367 326 typ, data = self._imap.subscribe(self._encode_folder_name(folder)) … … 369 328 return data 370 329 371 372 330 def unsubscribe_folder(self, folder): 373 """Unsubscribe a folder. 374 375 @param folder: Folder name to unsubscribe. 376 @return: Server response message. 331 """Unsubscribe to *folder*, returning the server response string. 377 332 """ 378 333 typ, data = self._imap.unsubscribe(self._encode_folder_name(folder)) … … 380 335 return data 381 336 382 383 337 def search(self, criteria='ALL', charset=None): 338 """Return a list of messages ids matching *criteria*. 339 340 XXX more detail 341 """ 384 342 if not criteria: 385 343 raise ValueError('no criteria specified') … … 406 364 407 365 def sort(self, sort_criteria, criteria='ALL', charset='UTF-8' ): 408 """Returns a list of messages sorted by sort_criteria. 366 """Return a list of message ids sorted by *sort_criteria* and 367 optionally filtered by *criteria*. 368 369 The *critera* are as per search(). 409 370 410 371 Note that this is an extension to the IMAP4: 411 372 http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-19.txt 373 374 XXX needs more detail 375 XXX explain charset 412 376 """ 413 377 if not criteria: … … 435 399 436 400 def get_flags(self, messages): 437 """Return the flags set for messages 438 439 @param messages: Message IDs to check flags for 440 @return: As for add_f 441 { msgid1: [flag1, flag2, ... ], } 401 """Returns the flags set for each message in *messages* as a 402 dictionary structured like this: 403 ``{ msgid1: [flag1, flag2, ... ], }``. 442 404 """ 443 405 response = self.fetch(messages, ['FLAGS']) … … 446 408 447 409 def add_flags(self, messages, flags): 448 """Add one or more flags to messages 449 450 @param messages: Message IDs to add flags to 451 @param flags: Sequence of flags to add 452 @return: The flags set for each message ID as a dictionary 453 { msgid1: [flag1, flag2, ... ], } 410 """Add *flags* to *messages*. 411 412 *flags* should be a sequence of strings. Returns the flags set 413 for each modified message (see *get_flags*). 454 414 """ 455 415 return self._store('+FLAGS', messages, flags) … … 457 417 458 418 def remove_flags(self, messages, flags): 459 """Remove one or more flags from messages 460 461 @param messages: Message IDs to remove flags from 462 @param flags: Sequence of flags to remove 463 @return: As for get_flags. 419 """Remove one or more *flags* from *messages*. 420 421 *flags* should be a sequence of strings. Returns the flags set 422 for each modified message (see *get_flags*). 464 423 """ 465 424 return self._store('-FLAGS', messages, flags) … … 467 426 468 427 def set_flags(self, messages, flags): 469 """Set the flags for messages 470 471 @param messages: Message IDs to set flags for 472 @param flags: Sequence of flags to set 473 @return: As for get_flags. 428 """Set the *flags* for *messages*. 429 430 *flags* should be a sequence of strings. Returns the flags set 431 for each modified message (see *get_flags*). 474 432 """ 475 433 return self._store('FLAGS', messages, flags) … … 477 435 478 436 def delete_messages(self, messages): 479 """Short-hand method for deleting one or more messages 480 481 @param messages: Message IDs to mark for deletion. 482 @return: Same as for get_flags. 437 """Delete one or more *messages* from the currently selected 438 folder. 439 440 Returns the flags set for each modified message (see 441 *get_flags*). 483 442 """ 484 443 return self.add_flags(messages, DELETED) … … 515 474 return parse_fetch_response(data) 516 475 517 518 476 def append(self, folder, msg, flags=(), msg_time=None): 519 """Append a message to a folder 520 521 @param folder: Folder name to append to. 522 @param msg: Message body as a string. 523 @param flags: Sequnce of message flags to set. If not specified no 524 flags will be set. 525 @param msg_time: Optional date and time to set for the message. The 526 server will set a time if it isn't specified. If msg_time contains 527 timezone information (tzinfo), this will be honoured. Otherwise the 528 local machine's time zone sent to the server. 529 @type msg_time: datetime.datetime 530 @return: The append response returned by the server. 531 @rtype: str 477 """Append a message to *folder*. 478 479 *msg* should be a string contains the full message including 480 headers. 481 482 *flags* should be a sequence of message flags to set. If not 483 specified no flags will be set. 484 485 *msg_time* is an optional datetime instance specifying the 486 date and time to set on the message. The server will set a 487 time if it isn't specified. If *msg_time* contains timezone 488 information (tzinfo), this will be honoured. Otherwise the 489 local machine's time zone sent to the server. 490 491 Returns the APPEND response as returned by the server. 532 492 """ 533 493 if msg_time: … … 544 504 return data[0] 545 505 546 547 506 def copy(self, messages, folder): 548 """Copy one or more messages from the current folder to another folder 549 550 @param messages: Message IDs to fetch. 551 @param folder: Folder name to append to. 552 @return: The COPY command response message returned by the 553 server. 507 """Copy one or more messages from the current folder to 508 *folder*. Returns the COPY response string returned by the 509 server. 554 510 """ 555 511 msg_list = messages_to_str(messages) … … 563 519 return data[0] 564 520 565 566 521 def expunge(self): 522 """Remove any messaages from the server that have the \Deleted 523 flag set. 524 525 XXX check if this is just for the current folder 526 """ 567 527 typ, data = self._imap.expunge() 568 528 self._checkok('expunge', typ, data) 569 529 #TODO: expunge response 570 530 571 572 531 def getacl(self, folder): 573 """Get the ACL for a folder 574 575 @param folder: Folder name to get the ACL for. 576 @return: A list of (who, acl) tuples 532 """Returns a list of ``(who, acl)`` tuples describing the 533 access controls for *folder*. 577 534 """ 578 535 typ, data = self._imap.getacl(folder) … … 587 544 return out 588 545 589 590 546 def setacl(self, folder, who, what): 591 """Set an ACL for a folder 592 593 @param folder: Folder name to set an ACL for. 594 @param who: User or group ID for the ACL. 595 @param what: A string describing the ACL. Set to '' to remove an ACL. 596 @return: Server response string. 547 """Set an ACL (*what*) for user (*who*) for a folder. 548 549 Set *what* to an empty string to remove an ACL. Returns the 550 server response string. 597 551 """ 598 552 typ, data = self._imap.setacl(folder, who, what) … … 600 554 return data[0] 601 555 602 603 556 def _check_resp(self, expected, command, typ, data): 604 557 """Check command responses for errors. 605 558 606 @raise: Error if a command failed.559 Raises IMAPClient.Error if the command fails. 607 560 """ 608 561 if typ != expected: 609 562 raise self.Error('%s failed: %r' % (command, data[0])) 610 563 611 612 564 def _checkok(self, command, typ, data): 613 565 self._check_resp('OK', command, typ, data) 614 566 615 616 567 def _checkbye(self, command, typ, data): 617 568 self._check_resp('BYE', command, typ, data) 618 569 619 620 570 def _store(self, cmd, messages, flags): 621 """Worker function for flag manipulation functions 622 623 @param cmd: STORE command to use (eg. '+FLAGS') 624 @param messages: Sequence of message IDs 625 @param flags: Sequence of flags to set. 626 @return: The flags set for each message ID as a dictionary 627 { msgid1: [flag1, flag2, ... ], } 571 """Worker function for the various flag manipulation methods. 572 573 *cmd* is the STORE command to use (eg. '+FLAGS'). 628 574 """ 629 575 if not messages: … … 639 585 self._checkok('store', typ, data) 640 586 return self._flatten_dict(parse_fetch_response((data))) 641 642 587 643 588 def _flatten_dict(self, fetch_dict): … … 651 596 return imap_utf7.decode(name) 652 597 return name 653 654 598 655 599 def _encode_folder_name(self, name): … … 669 613 670 614 def messages_to_str(messages): 671 """Convert a sequence of messages ids or a single message id into an 672 message ID list for use with IMAP commands. 673 674 @param messages: A sequence of messages IDs or a single message ID. 675 (eg. [1,4,5,7,8]) 676 @return: Message list string (eg. "1,4,5,6,8") 615 """Convert a sequence of messages ids or a single integer message id 616 into an id list string for use with IMAP commands 677 617 """ 678 618 if isinstance(messages, (str, int, long)): … … 684 624 685 625 def seq_to_parenlist(flags): 686 """Convert a sequence into parenthised list for use with IMAP commands 687 688 @param flags: Sequence to process (eg. ['abc', 'def']) 689 @return: IMAP parenthenised list (eg. '(abc def)') 626 """Convert a sequence of strings into parenthised list string for 627 use with IMAP commands. 690 628 """ 691 629 if isinstance(flags, str): … … 697 635 698 636 def datetime_to_imap(dt): 699 """Convert a datetime instance to a IMAP datetime string 700 701 If timezone information is missing the current system timezone is used. 637 """Convert a datetime instance to a IMAP datetime string. 638 639 If timezone information is missing the current system 640 timezone is used. 702 641 """ 703 642 if not dt.tzinfo:
