/gdata/data.py
Python | 1186 lines | 1058 code | 66 blank | 62 comment | 16 complexity | 37c107a6020c086c65bf681496e76015 MD5 | raw file
1#!/usr/bin/env python 2# 3# Copyright (C) 2009 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17 18# This module is used for version 2 of the Google Data APIs. 19 20 21"""Provides classes and constants for the XML in the Google Data namespace. 22 23Documentation for the raw XML which these classes represent can be found here: 24http://code.google.com/apis/gdata/docs/2.0/elements.html 25""" 26 27 28__author__ = 'j.s@google.com (Jeff Scudder)' 29 30 31import os 32import atom.core 33import atom.data 34 35 36GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s' 37GD_TEMPLATE = GDATA_TEMPLATE 38OPENSEARCH_TEMPLATE_V1 = '{http://a9.com/-/spec/opensearchrss/1.0/}%s' 39OPENSEARCH_TEMPLATE_V2 = '{http://a9.com/-/spec/opensearch/1.1/}%s' 40BATCH_TEMPLATE = '{http://schemas.google.com/gdata/batch}%s' 41 42 43# Labels used in batch request entries to specify the desired CRUD operation. 44BATCH_INSERT = 'insert' 45BATCH_UPDATE = 'update' 46BATCH_DELETE = 'delete' 47BATCH_QUERY = 'query' 48 49EVENT_LOCATION = 'http://schemas.google.com/g/2005#event' 50ALTERNATE_LOCATION = 'http://schemas.google.com/g/2005#event.alternate' 51PARKING_LOCATION = 'http://schemas.google.com/g/2005#event.parking' 52 53CANCELED_EVENT = 'http://schemas.google.com/g/2005#event.canceled' 54CONFIRMED_EVENT = 'http://schemas.google.com/g/2005#event.confirmed' 55TENTATIVE_EVENT = 'http://schemas.google.com/g/2005#event.tentative' 56 57CONFIDENTIAL_EVENT = 'http://schemas.google.com/g/2005#event.confidential' 58DEFAULT_EVENT = 'http://schemas.google.com/g/2005#event.default' 59PRIVATE_EVENT = 'http://schemas.google.com/g/2005#event.private' 60PUBLIC_EVENT = 'http://schemas.google.com/g/2005#event.public' 61 62OPAQUE_EVENT = 'http://schemas.google.com/g/2005#event.opaque' 63TRANSPARENT_EVENT = 'http://schemas.google.com/g/2005#event.transparent' 64 65CHAT_MESSAGE = 'http://schemas.google.com/g/2005#message.chat' 66INBOX_MESSAGE = 'http://schemas.google.com/g/2005#message.inbox' 67SENT_MESSAGE = 'http://schemas.google.com/g/2005#message.sent' 68SPAM_MESSAGE = 'http://schemas.google.com/g/2005#message.spam' 69STARRED_MESSAGE = 'http://schemas.google.com/g/2005#message.starred' 70UNREAD_MESSAGE = 'http://schemas.google.com/g/2005#message.unread' 71 72BCC_RECIPIENT = 'http://schemas.google.com/g/2005#message.bcc' 73CC_RECIPIENT = 'http://schemas.google.com/g/2005#message.cc' 74SENDER = 'http://schemas.google.com/g/2005#message.from' 75REPLY_TO = 'http://schemas.google.com/g/2005#message.reply-to' 76TO_RECIPIENT = 'http://schemas.google.com/g/2005#message.to' 77 78ASSISTANT_REL = 'http://schemas.google.com/g/2005#assistant' 79CALLBACK_REL = 'http://schemas.google.com/g/2005#callback' 80CAR_REL = 'http://schemas.google.com/g/2005#car' 81COMPANY_MAIN_REL = 'http://schemas.google.com/g/2005#company_main' 82FAX_REL = 'http://schemas.google.com/g/2005#fax' 83HOME_REL = 'http://schemas.google.com/g/2005#home' 84HOME_FAX_REL = 'http://schemas.google.com/g/2005#home_fax' 85ISDN_REL = 'http://schemas.google.com/g/2005#isdn' 86MAIN_REL = 'http://schemas.google.com/g/2005#main' 87MOBILE_REL = 'http://schemas.google.com/g/2005#mobile' 88OTHER_REL = 'http://schemas.google.com/g/2005#other' 89OTHER_FAX_REL = 'http://schemas.google.com/g/2005#other_fax' 90PAGER_REL = 'http://schemas.google.com/g/2005#pager' 91RADIO_REL = 'http://schemas.google.com/g/2005#radio' 92TELEX_REL = 'http://schemas.google.com/g/2005#telex' 93TTL_TDD_REL = 'http://schemas.google.com/g/2005#tty_tdd' 94WORK_REL = 'http://schemas.google.com/g/2005#work' 95WORK_FAX_REL = 'http://schemas.google.com/g/2005#work_fax' 96WORK_MOBILE_REL = 'http://schemas.google.com/g/2005#work_mobile' 97WORK_PAGER_REL = 'http://schemas.google.com/g/2005#work_pager' 98NETMEETING_REL = 'http://schemas.google.com/g/2005#netmeeting' 99OVERALL_REL = 'http://schemas.google.com/g/2005#overall' 100PRICE_REL = 'http://schemas.google.com/g/2005#price' 101QUALITY_REL = 'http://schemas.google.com/g/2005#quality' 102EVENT_REL = 'http://schemas.google.com/g/2005#event' 103EVENT_ALTERNATE_REL = 'http://schemas.google.com/g/2005#event.alternate' 104EVENT_PARKING_REL = 'http://schemas.google.com/g/2005#event.parking' 105 106AIM_PROTOCOL = 'http://schemas.google.com/g/2005#AIM' 107MSN_PROTOCOL = 'http://schemas.google.com/g/2005#MSN' 108YAHOO_MESSENGER_PROTOCOL = 'http://schemas.google.com/g/2005#YAHOO' 109SKYPE_PROTOCOL = 'http://schemas.google.com/g/2005#SKYPE' 110QQ_PROTOCOL = 'http://schemas.google.com/g/2005#QQ' 111GOOGLE_TALK_PROTOCOL = 'http://schemas.google.com/g/2005#GOOGLE_TALK' 112ICQ_PROTOCOL = 'http://schemas.google.com/g/2005#ICQ' 113JABBER_PROTOCOL = 'http://schemas.google.com/g/2005#JABBER' 114 115REGULAR_COMMENTS = 'http://schemas.google.com/g/2005#regular' 116REVIEW_COMMENTS = 'http://schemas.google.com/g/2005#reviews' 117 118MAIL_BOTH = 'http://schemas.google.com/g/2005#both' 119MAIL_LETTERS = 'http://schemas.google.com/g/2005#letters' 120MAIL_PARCELS = 'http://schemas.google.com/g/2005#parcels' 121MAIL_NEITHER = 'http://schemas.google.com/g/2005#neither' 122 123GENERAL_ADDRESS = 'http://schemas.google.com/g/2005#general' 124LOCAL_ADDRESS = 'http://schemas.google.com/g/2005#local' 125 126OPTIONAL_ATENDEE = 'http://schemas.google.com/g/2005#event.optional' 127REQUIRED_ATENDEE = 'http://schemas.google.com/g/2005#event.required' 128 129ATTENDEE_ACCEPTED = 'http://schemas.google.com/g/2005#event.accepted' 130ATTENDEE_DECLINED = 'http://schemas.google.com/g/2005#event.declined' 131ATTENDEE_INVITED = 'http://schemas.google.com/g/2005#event.invited' 132ATTENDEE_TENTATIVE = 'http://schemas.google.com/g/2005#event.tentative' 133 134FULL_PROJECTION = 'full' 135VALUES_PROJECTION = 'values' 136BASIC_PROJECTION = 'basic' 137 138PRIVATE_VISIBILITY = 'private' 139PUBLIC_VISIBILITY = 'public' 140 141ACL_REL = 'http://schemas.google.com/acl/2007#accessControlList' 142 143 144class Error(Exception): 145 pass 146 147 148class MissingRequiredParameters(Error): 149 pass 150 151 152class LinkFinder(atom.data.LinkFinder): 153 """Mixin used in Feed and Entry classes to simplify link lookups by type. 154 155 Provides lookup methods for edit, edit-media, post, ACL and other special 156 links which are common across Google Data APIs. 157 """ 158 159 def find_html_link(self): 160 """Finds the first link with rel of alternate and type of text/html.""" 161 for link in self.link: 162 if link.rel == 'alternate' and link.type == 'text/html': 163 return link.href 164 return None 165 166 FindHtmlLink = find_html_link 167 168 def get_html_link(self): 169 for a_link in self.link: 170 if a_link.rel == 'alternate' and a_link.type == 'text/html': 171 return a_link 172 return None 173 174 GetHtmlLink = get_html_link 175 176 def find_post_link(self): 177 """Get the URL to which new entries should be POSTed. 178 179 The POST target URL is used to insert new entries. 180 181 Returns: 182 A str for the URL in the link with a rel matching the POST type. 183 """ 184 return self.find_url('http://schemas.google.com/g/2005#post') 185 186 FindPostLink = find_post_link 187 188 def get_post_link(self): 189 return self.get_link('http://schemas.google.com/g/2005#post') 190 191 GetPostLink = get_post_link 192 193 def find_acl_link(self): 194 acl_link = self.get_acl_link() 195 if acl_link: 196 return acl_link.href 197 198 return None 199 200 FindAclLink = find_acl_link 201 202 def get_acl_link(self): 203 """Searches for a link or feed_link (if present) with the rel for ACL.""" 204 205 acl_link = self.get_link(ACL_REL) 206 if acl_link: 207 return acl_link 208 elif hasattr(self, 'feed_link'): 209 for a_feed_link in self.feed_link: 210 if a_feed_link.rel == ACL_REL: 211 return a_feed_link 212 213 return None 214 215 GetAclLink = get_acl_link 216 217 def find_feed_link(self): 218 return self.find_url('http://schemas.google.com/g/2005#feed') 219 220 FindFeedLink = find_feed_link 221 222 def get_feed_link(self): 223 return self.get_link('http://schemas.google.com/g/2005#feed') 224 225 GetFeedLink = get_feed_link 226 227 def find_previous_link(self): 228 return self.find_url('previous') 229 230 FindPreviousLink = find_previous_link 231 232 def get_previous_link(self): 233 return self.get_link('previous') 234 235 GetPreviousLink = get_previous_link 236 237 238class TotalResults(atom.core.XmlElement): 239 """opensearch:TotalResults for a GData feed.""" 240 _qname = (OPENSEARCH_TEMPLATE_V1 % 'totalResults', 241 OPENSEARCH_TEMPLATE_V2 % 'totalResults') 242 243 244class StartIndex(atom.core.XmlElement): 245 """The opensearch:startIndex element in GData feed.""" 246 _qname = (OPENSEARCH_TEMPLATE_V1 % 'startIndex', 247 OPENSEARCH_TEMPLATE_V2 % 'startIndex') 248 249 250class ItemsPerPage(atom.core.XmlElement): 251 """The opensearch:itemsPerPage element in GData feed.""" 252 _qname = (OPENSEARCH_TEMPLATE_V1 % 'itemsPerPage', 253 OPENSEARCH_TEMPLATE_V2 % 'itemsPerPage') 254 255 256class ExtendedProperty(atom.core.XmlElement): 257 """The Google Data extendedProperty element. 258 259 Used to store arbitrary key-value information specific to your 260 application. The value can either be a text string stored as an XML 261 attribute (.value), or an XML node (XmlBlob) as a child element. 262 263 This element is used in the Google Calendar data API and the Google 264 Contacts data API. 265 """ 266 _qname = GDATA_TEMPLATE % 'extendedProperty' 267 name = 'name' 268 value = 'value' 269 270 def get_xml_blob(self): 271 """Returns the XML blob as an atom.core.XmlElement. 272 273 Returns: 274 An XmlElement representing the blob's XML, or None if no 275 blob was set. 276 """ 277 if self._other_elements: 278 return self._other_elements[0] 279 else: 280 return None 281 282 GetXmlBlob = get_xml_blob 283 284 def set_xml_blob(self, blob): 285 """Sets the contents of the extendedProperty to XML as a child node. 286 287 Since the extendedProperty is only allowed one child element as an XML 288 blob, setting the XML blob will erase any preexisting member elements 289 in this object. 290 291 Args: 292 blob: str or atom.core.XmlElement representing the XML blob stored in 293 the extendedProperty. 294 """ 295 # Erase any existing extension_elements, clears the child nodes from the 296 # extendedProperty. 297 if isinstance(blob, atom.core.XmlElement): 298 self._other_elements = [blob] 299 else: 300 self._other_elements = [atom.core.parse(str(blob))] 301 302 SetXmlBlob = set_xml_blob 303 304 305class GDEntry(atom.data.Entry, LinkFinder): 306 """Extends Atom Entry to provide data processing""" 307 etag = '{http://schemas.google.com/g/2005}etag' 308 309 def get_id(self): 310 if self.id is not None and self.id.text is not None: 311 return self.id.text.strip() 312 return None 313 314 GetId = get_id 315 316 def is_media(self): 317 if self.find_media_edit_link(): 318 return True 319 return False 320 321 IsMedia = is_media 322 323 def find_media_link(self): 324 """Returns the URL to the media content, if the entry is a media entry. 325 Otherwise returns None. 326 """ 327 if self.is_media(): 328 return self.content.src 329 return None 330 331 FindMediaLink = find_media_link 332 333 334class GDFeed(atom.data.Feed, LinkFinder): 335 """A Feed from a GData service.""" 336 etag = '{http://schemas.google.com/g/2005}etag' 337 total_results = TotalResults 338 start_index = StartIndex 339 items_per_page = ItemsPerPage 340 entry = [GDEntry] 341 342 def get_id(self): 343 if self.id is not None and self.id.text is not None: 344 return self.id.text.strip() 345 return None 346 347 GetId = get_id 348 349 def get_generator(self): 350 if self.generator and self.generator.text: 351 return self.generator.text.strip() 352 return None 353 354 355class BatchId(atom.core.XmlElement): 356 """Identifies a single operation in a batch request.""" 357 _qname = BATCH_TEMPLATE % 'id' 358 359 360class BatchOperation(atom.core.XmlElement): 361 """The CRUD operation which this batch entry represents.""" 362 _qname = BATCH_TEMPLATE % 'operation' 363 type = 'type' 364 365 366class BatchStatus(atom.core.XmlElement): 367 """The batch:status element present in a batch response entry. 368 369 A status element contains the code (HTTP response code) and 370 reason as elements. In a single request these fields would 371 be part of the HTTP response, but in a batch request each 372 Entry operation has a corresponding Entry in the response 373 feed which includes status information. 374 375 See http://code.google.com/apis/gdata/batch.html#Handling_Errors 376 """ 377 _qname = BATCH_TEMPLATE % 'status' 378 code = 'code' 379 reason = 'reason' 380 content_type = 'content-type' 381 382 383class BatchEntry(GDEntry): 384 """An atom:entry for use in batch requests. 385 386 The BatchEntry contains additional members to specify the operation to be 387 performed on this entry and a batch ID so that the server can reference 388 individual operations in the response feed. For more information, see: 389 http://code.google.com/apis/gdata/batch.html 390 """ 391 batch_operation = BatchOperation 392 batch_id = BatchId 393 batch_status = BatchStatus 394 395 396class BatchInterrupted(atom.core.XmlElement): 397 """The batch:interrupted element sent if batch request was interrupted. 398 399 Only appears in a feed if some of the batch entries could not be processed. 400 See: http://code.google.com/apis/gdata/batch.html#Handling_Errors 401 """ 402 _qname = BATCH_TEMPLATE % 'interrupted' 403 reason = 'reason' 404 success = 'success' 405 failures = 'failures' 406 parsed = 'parsed' 407 408 409class BatchFeed(GDFeed): 410 """A feed containing a list of batch request entries.""" 411 interrupted = BatchInterrupted 412 entry = [BatchEntry] 413 414 def add_batch_entry(self, entry=None, id_url_string=None, 415 batch_id_string=None, operation_string=None): 416 """Logic for populating members of a BatchEntry and adding to the feed. 417 418 If the entry is not a BatchEntry, it is converted to a BatchEntry so 419 that the batch specific members will be present. 420 421 The id_url_string can be used in place of an entry if the batch operation 422 applies to a URL. For example query and delete operations require just 423 the URL of an entry, no body is sent in the HTTP request. If an 424 id_url_string is sent instead of an entry, a BatchEntry is created and 425 added to the feed. 426 427 This method also assigns the desired batch id to the entry so that it 428 can be referenced in the server's response. If the batch_id_string is 429 None, this method will assign a batch_id to be the index at which this 430 entry will be in the feed's entry list. 431 432 Args: 433 entry: BatchEntry, atom.data.Entry, or another Entry flavor (optional) 434 The entry which will be sent to the server as part of the batch 435 request. The item must have a valid atom id so that the server 436 knows which entry this request references. 437 id_url_string: str (optional) The URL of the entry to be acted on. You 438 can find this URL in the text member of the atom id for an entry. 439 If an entry is not sent, this id will be used to construct a new 440 BatchEntry which will be added to the request feed. 441 batch_id_string: str (optional) The batch ID to be used to reference 442 this batch operation in the results feed. If this parameter is None, 443 the current length of the feed's entry array will be used as a 444 count. Note that batch_ids should either always be specified or 445 never, mixing could potentially result in duplicate batch ids. 446 operation_string: str (optional) The desired batch operation which will 447 set the batch_operation.type member of the entry. Options are 448 'insert', 'update', 'delete', and 'query' 449 450 Raises: 451 MissingRequiredParameters: Raised if neither an id_ url_string nor an 452 entry are provided in the request. 453 454 Returns: 455 The added entry. 456 """ 457 if entry is None and id_url_string is None: 458 raise MissingRequiredParameters('supply either an entry or URL string') 459 if entry is None and id_url_string is not None: 460 entry = BatchEntry(id=atom.data.Id(text=id_url_string)) 461 if batch_id_string is not None: 462 entry.batch_id = BatchId(text=batch_id_string) 463 elif entry.batch_id is None or entry.batch_id.text is None: 464 entry.batch_id = BatchId(text=str(len(self.entry))) 465 if operation_string is not None: 466 entry.batch_operation = BatchOperation(type=operation_string) 467 self.entry.append(entry) 468 return entry 469 470 AddBatchEntry = add_batch_entry 471 472 def add_insert(self, entry, batch_id_string=None): 473 """Add an insert request to the operations in this batch request feed. 474 475 If the entry doesn't yet have an operation or a batch id, these will 476 be set to the insert operation and a batch_id specified as a parameter. 477 478 Args: 479 entry: BatchEntry The entry which will be sent in the batch feed as an 480 insert request. 481 batch_id_string: str (optional) The batch ID to be used to reference 482 this batch operation in the results feed. If this parameter is None, 483 the current length of the feed's entry array will be used as a 484 count. Note that batch_ids should either always be specified or 485 never, mixing could potentially result in duplicate batch ids. 486 """ 487 self.add_batch_entry(entry=entry, batch_id_string=batch_id_string, 488 operation_string=BATCH_INSERT) 489 490 AddInsert = add_insert 491 492 def add_update(self, entry, batch_id_string=None): 493 """Add an update request to the list of batch operations in this feed. 494 495 Sets the operation type of the entry to insert if it is not already set 496 and assigns the desired batch id to the entry so that it can be 497 referenced in the server's response. 498 499 Args: 500 entry: BatchEntry The entry which will be sent to the server as an 501 update (HTTP PUT) request. The item must have a valid atom id 502 so that the server knows which entry to replace. 503 batch_id_string: str (optional) The batch ID to be used to reference 504 this batch operation in the results feed. If this parameter is None, 505 the current length of the feed's entry array will be used as a 506 count. See also comments for AddInsert. 507 """ 508 self.add_batch_entry(entry=entry, batch_id_string=batch_id_string, 509 operation_string=BATCH_UPDATE) 510 511 AddUpdate = add_update 512 513 def add_delete(self, url_string=None, entry=None, batch_id_string=None): 514 """Adds a delete request to the batch request feed. 515 516 This method takes either the url_string which is the atom id of the item 517 to be deleted, or the entry itself. The atom id of the entry must be 518 present so that the server knows which entry should be deleted. 519 520 Args: 521 url_string: str (optional) The URL of the entry to be deleted. You can 522 find this URL in the text member of the atom id for an entry. 523 entry: BatchEntry (optional) The entry to be deleted. 524 batch_id_string: str (optional) 525 526 Raises: 527 MissingRequiredParameters: Raised if neither a url_string nor an entry 528 are provided in the request. 529 """ 530 self.add_batch_entry(entry=entry, id_url_string=url_string, 531 batch_id_string=batch_id_string, operation_string=BATCH_DELETE) 532 533 AddDelete = add_delete 534 535 def add_query(self, url_string=None, entry=None, batch_id_string=None): 536 """Adds a query request to the batch request feed. 537 538 This method takes either the url_string which is the query URL 539 whose results will be added to the result feed. The query URL will 540 be encapsulated in a BatchEntry, and you may pass in the BatchEntry 541 with a query URL instead of sending a url_string. 542 543 Args: 544 url_string: str (optional) 545 entry: BatchEntry (optional) 546 batch_id_string: str (optional) 547 548 Raises: 549 MissingRequiredParameters 550 """ 551 self.add_batch_entry(entry=entry, id_url_string=url_string, 552 batch_id_string=batch_id_string, operation_string=BATCH_QUERY) 553 554 AddQuery = add_query 555 556 def find_batch_link(self): 557 return self.find_url('http://schemas.google.com/g/2005#batch') 558 559 FindBatchLink = find_batch_link 560 561 562class EntryLink(atom.core.XmlElement): 563 """The gd:entryLink element. 564 565 Represents a logically nested entry. For example, a <gd:who> 566 representing a contact might have a nested entry from a contact feed. 567 """ 568 _qname = GDATA_TEMPLATE % 'entryLink' 569 entry = GDEntry 570 rel = 'rel' 571 read_only = 'readOnly' 572 href = 'href' 573 574 575class FeedLink(atom.core.XmlElement): 576 """The gd:feedLink element. 577 578 Represents a logically nested feed. For example, a calendar feed might 579 have a nested feed representing all comments on entries. 580 """ 581 _qname = GDATA_TEMPLATE % 'feedLink' 582 feed = GDFeed 583 rel = 'rel' 584 read_only = 'readOnly' 585 count_hint = 'countHint' 586 href = 'href' 587 588 589class AdditionalName(atom.core.XmlElement): 590 """The gd:additionalName element. 591 592 Specifies additional (eg. middle) name of the person. 593 Contains an attribute for the phonetic representaton of the name. 594 """ 595 _qname = GDATA_TEMPLATE % 'additionalName' 596 yomi = 'yomi' 597 598 599class Comments(atom.core.XmlElement): 600 """The gd:comments element. 601 602 Contains a comments feed for the enclosing entry (such as a calendar event). 603 """ 604 _qname = GDATA_TEMPLATE % 'comments' 605 rel = 'rel' 606 feed_link = FeedLink 607 608 609class Country(atom.core.XmlElement): 610 """The gd:country element. 611 612 Country name along with optional country code. The country code is 613 given in accordance with ISO 3166-1 alpha-2: 614 http://www.iso.org/iso/iso-3166-1_decoding_table 615 """ 616 _qname = GDATA_TEMPLATE % 'country' 617 code = 'code' 618 619 620class EmailImParent(atom.core.XmlElement): 621 address = 'address' 622 label = 'label' 623 rel = 'rel' 624 primary = 'primary' 625 626 627class Email(EmailImParent): 628 """The gd:email element. 629 630 An email address associated with the containing entity (which is 631 usually an entity representing a person or a location). 632 """ 633 _qname = GDATA_TEMPLATE % 'email' 634 display_name = 'displayName' 635 636 637class FamilyName(atom.core.XmlElement): 638 """The gd:familyName element. 639 640 Specifies family name of the person, eg. "Smith". 641 """ 642 _qname = GDATA_TEMPLATE % 'familyName' 643 yomi = 'yomi' 644 645 646class Im(EmailImParent): 647 """The gd:im element. 648 649 An instant messaging address associated with the containing entity. 650 """ 651 _qname = GDATA_TEMPLATE % 'im' 652 protocol = 'protocol' 653 654 655class GivenName(atom.core.XmlElement): 656 """The gd:givenName element. 657 658 Specifies given name of the person, eg. "John". 659 """ 660 _qname = GDATA_TEMPLATE % 'givenName' 661 yomi = 'yomi' 662 663 664class NamePrefix(atom.core.XmlElement): 665 """The gd:namePrefix element. 666 667 Honorific prefix, eg. 'Mr' or 'Mrs'. 668 """ 669 _qname = GDATA_TEMPLATE % 'namePrefix' 670 671 672class NameSuffix(atom.core.XmlElement): 673 """The gd:nameSuffix element. 674 675 Honorific suffix, eg. 'san' or 'III'. 676 """ 677 _qname = GDATA_TEMPLATE % 'nameSuffix' 678 679 680class FullName(atom.core.XmlElement): 681 """The gd:fullName element. 682 683 Unstructured representation of the name. 684 """ 685 _qname = GDATA_TEMPLATE % 'fullName' 686 687 688class Name(atom.core.XmlElement): 689 """The gd:name element. 690 691 Allows storing person's name in a structured way. Consists of 692 given name, additional name, family name, prefix, suffix and full name. 693 """ 694 _qname = GDATA_TEMPLATE % 'name' 695 given_name = GivenName 696 additional_name = AdditionalName 697 family_name = FamilyName 698 name_prefix = NamePrefix 699 name_suffix = NameSuffix 700 full_name = FullName 701 702 703class OrgDepartment(atom.core.XmlElement): 704 """The gd:orgDepartment element. 705 706 Describes a department within an organization. Must appear within a 707 gd:organization element. 708 """ 709 _qname = GDATA_TEMPLATE % 'orgDepartment' 710 711 712class OrgJobDescription(atom.core.XmlElement): 713 """The gd:orgJobDescription element. 714 715 Describes a job within an organization. Must appear within a 716 gd:organization element. 717 """ 718 _qname = GDATA_TEMPLATE % 'orgJobDescription' 719 720 721class OrgName(atom.core.XmlElement): 722 """The gd:orgName element. 723 724 The name of the organization. Must appear within a gd:organization 725 element. 726 727 Contains a Yomigana attribute (Japanese reading aid) for the 728 organization name. 729 """ 730 _qname = GDATA_TEMPLATE % 'orgName' 731 yomi = 'yomi' 732 733 734class OrgSymbol(atom.core.XmlElement): 735 """The gd:orgSymbol element. 736 737 Provides a symbol of an organization. Must appear within a 738 gd:organization element. 739 """ 740 _qname = GDATA_TEMPLATE % 'orgSymbol' 741 742 743class OrgTitle(atom.core.XmlElement): 744 """The gd:orgTitle element. 745 746 The title of a person within an organization. Must appear within a 747 gd:organization element. 748 """ 749 _qname = GDATA_TEMPLATE % 'orgTitle' 750 751 752class Organization(atom.core.XmlElement): 753 """The gd:organization element. 754 755 An organization, typically associated with a contact. 756 """ 757 _qname = GDATA_TEMPLATE % 'organization' 758 label = 'label' 759 primary = 'primary' 760 rel = 'rel' 761 department = OrgDepartment 762 job_description = OrgJobDescription 763 name = OrgName 764 symbol = OrgSymbol 765 title = OrgTitle 766 767 768class When(atom.core.XmlElement): 769 """The gd:when element. 770 771 Represents a period of time or an instant. 772 """ 773 _qname = GDATA_TEMPLATE % 'when' 774 end = 'endTime' 775 start = 'startTime' 776 value = 'valueString' 777 778 779class OriginalEvent(atom.core.XmlElement): 780 """The gd:originalEvent element. 781 782 Equivalent to the Recurrence ID property specified in section 4.8.4.4 783 of RFC 2445. Appears in every instance of a recurring event, to identify 784 the original event. 785 786 Contains a <gd:when> element specifying the original start time of the 787 instance that has become an exception. 788 """ 789 _qname = GDATA_TEMPLATE % 'originalEvent' 790 id = 'id' 791 href = 'href' 792 when = When 793 794 795class PhoneNumber(atom.core.XmlElement): 796 """The gd:phoneNumber element. 797 798 A phone number associated with the containing entity (which is usually 799 an entity representing a person or a location). 800 """ 801 _qname = GDATA_TEMPLATE % 'phoneNumber' 802 label = 'label' 803 rel = 'rel' 804 uri = 'uri' 805 primary = 'primary' 806 807 808class PostalAddress(atom.core.XmlElement): 809 """The gd:postalAddress element.""" 810 _qname = GDATA_TEMPLATE % 'postalAddress' 811 label = 'label' 812 rel = 'rel' 813 uri = 'uri' 814 primary = 'primary' 815 816 817class Rating(atom.core.XmlElement): 818 """The gd:rating element. 819 820 Represents a numeric rating of the enclosing entity, such as a 821 comment. Each rating supplies its own scale, although it may be 822 normalized by a service; for example, some services might convert all 823 ratings to a scale from 1 to 5. 824 """ 825 _qname = GDATA_TEMPLATE % 'rating' 826 average = 'average' 827 max = 'max' 828 min = 'min' 829 num_raters = 'numRaters' 830 rel = 'rel' 831 value = 'value' 832 833 834class Recurrence(atom.core.XmlElement): 835 """The gd:recurrence element. 836 837 Represents the dates and times when a recurring event takes place. 838 839 The string that defines the recurrence consists of a set of properties, 840 each of which is defined in the iCalendar standard (RFC 2445). 841 842 Specifically, the string usually begins with a DTSTART property that 843 indicates the starting time of the first instance of the event, and 844 often a DTEND property or a DURATION property to indicate when the 845 first instance ends. Next come RRULE, RDATE, EXRULE, and/or EXDATE 846 properties, which collectively define a recurring event and its 847 exceptions (but see below). (See section 4.8.5 of RFC 2445 for more 848 information about these recurrence component properties.) Last comes a 849 VTIMEZONE component, providing detailed timezone rules for any timezone 850 ID mentioned in the preceding properties. 851 852 Google services like Google Calendar don't generally generate EXRULE 853 and EXDATE properties to represent exceptions to recurring events; 854 instead, they generate <gd:recurrenceException> elements. However, 855 Google services may include EXRULE and/or EXDATE properties anyway; 856 for example, users can import events and exceptions into Calendar, and 857 if those imported events contain EXRULE or EXDATE properties, then 858 Calendar will provide those properties when it sends a <gd:recurrence> 859 element. 860 861 Note the the use of <gd:recurrenceException> means that you can't be 862 sure just from examining a <gd:recurrence> element whether there are 863 any exceptions to the recurrence description. To ensure that you find 864 all exceptions, look for <gd:recurrenceException> elements in the feed, 865 and use their <gd:originalEvent> elements to match them up with 866 <gd:recurrence> elements. 867 """ 868 _qname = GDATA_TEMPLATE % 'recurrence' 869 870 871class RecurrenceException(atom.core.XmlElement): 872 """The gd:recurrenceException element. 873 874 Represents an event that's an exception to a recurring event-that is, 875 an instance of a recurring event in which one or more aspects of the 876 recurring event (such as attendance list, time, or location) have been 877 changed. 878 879 Contains a <gd:originalEvent> element that specifies the original 880 recurring event that this event is an exception to. 881 882 When you change an instance of a recurring event, that instance becomes 883 an exception. Depending on what change you made to it, the exception 884 behaves in either of two different ways when the original recurring 885 event is changed: 886 887 - If you add, change, or remove comments, attendees, or attendee 888 responses, then the exception remains tied to the original event, and 889 changes to the original event also change the exception. 890 - If you make any other changes to the exception (such as changing the 891 time or location) then the instance becomes "specialized," which means 892 that it's no longer as tightly tied to the original event. If you 893 change the original event, specialized exceptions don't change. But 894 see below. 895 896 For example, say you have a meeting every Tuesday and Thursday at 897 2:00 p.m. If you change the attendance list for this Thursday's meeting 898 (but not for the regularly scheduled meeting), then it becomes an 899 exception. If you change the time for this Thursday's meeting (but not 900 for the regularly scheduled meeting), then it becomes specialized. 901 902 Regardless of whether an exception is specialized or not, if you do 903 something that deletes the instance that the exception was derived from, 904 then the exception is deleted. Note that changing the day or time of a 905 recurring event deletes all instances, and creates new ones. 906 907 For example, after you've specialized this Thursday's meeting, say you 908 change the recurring meeting to happen on Monday, Wednesday, and Friday. 909 That change deletes all of the recurring instances of the 910 Tuesday/Thursday meeting, including the specialized one. 911 912 If a particular instance of a recurring event is deleted, then that 913 instance appears as a <gd:recurrenceException> containing a 914 <gd:entryLink> that has its <gd:eventStatus> set to 915 "http://schemas.google.com/g/2005#event.canceled". (For more 916 information about canceled events, see RFC 2445.) 917 """ 918 _qname = GDATA_TEMPLATE % 'recurrenceException' 919 specialized = 'specialized' 920 entry_link = EntryLink 921 original_event = OriginalEvent 922 923 924class Reminder(atom.core.XmlElement): 925 """The gd:reminder element. 926 927 A time interval, indicating how long before the containing entity's start 928 time or due time attribute a reminder should be issued. Alternatively, 929 may specify an absolute time at which a reminder should be issued. Also 930 specifies a notification method, indicating what medium the system 931 should use to remind the user. 932 """ 933 _qname = GDATA_TEMPLATE % 'reminder' 934 absolute_time = 'absoluteTime' 935 method = 'method' 936 days = 'days' 937 hours = 'hours' 938 minutes = 'minutes' 939 940 941class Agent(atom.core.XmlElement): 942 """The gd:agent element. 943 944 The agent who actually receives the mail. Used in work addresses. 945 Also for 'in care of' or 'c/o'. 946 """ 947 _qname = GDATA_TEMPLATE % 'agent' 948 949 950class HouseName(atom.core.XmlElement): 951 """The gd:housename element. 952 953 Used in places where houses or buildings have names (and not 954 necessarily numbers), eg. "The Pillars". 955 """ 956 _qname = GDATA_TEMPLATE % 'housename' 957 958 959class Street(atom.core.XmlElement): 960 """The gd:street element. 961 962 Can be street, avenue, road, etc. This element also includes the 963 house number and room/apartment/flat/floor number. 964 """ 965 _qname = GDATA_TEMPLATE % 'street' 966 967 968class PoBox(atom.core.XmlElement): 969 """The gd:pobox element. 970 971 Covers actual P.O. boxes, drawers, locked bags, etc. This is usually 972 but not always mutually exclusive with street. 973 """ 974 _qname = GDATA_TEMPLATE % 'pobox' 975 976 977class Neighborhood(atom.core.XmlElement): 978 """The gd:neighborhood element. 979 980 This is used to disambiguate a street address when a city contains more 981 than one street with the same name, or to specify a small place whose 982 mail is routed through a larger postal town. In China it could be a 983 county or a minor city. 984 """ 985 _qname = GDATA_TEMPLATE % 'neighborhood' 986 987 988class City(atom.core.XmlElement): 989 """The gd:city element. 990 991 Can be city, village, town, borough, etc. This is the postal town and 992 not necessarily the place of residence or place of business. 993 """ 994 _qname = GDATA_TEMPLATE % 'city' 995 996 997class Subregion(atom.core.XmlElement): 998 """The gd:subregion element. 999 1000 Handles administrative districts such as U.S. or U.K. counties that are 1001 not used for mail addressing purposes. Subregion is not intended for 1002 delivery addresses. 1003 """ 1004 _qname = GDATA_TEMPLATE % 'subregion' 1005 1006 1007class Region(atom.core.XmlElement): 1008 """The gd:region element. 1009 1010 A state, province, county (in Ireland), Land (in Germany), 1011 departement (in France), etc. 1012 """ 1013 _qname = GDATA_TEMPLATE % 'region' 1014 1015 1016class Postcode(atom.core.XmlElement): 1017 """The gd:postcode element. 1018 1019 Postal code. Usually country-wide, but sometimes specific to the 1020 city (e.g. "2" in "Dublin 2, Ireland" addresses). 1021 """ 1022 _qname = GDATA_TEMPLATE % 'postcode' 1023 1024 1025class Country(atom.core.XmlElement): 1026 """The gd:country element. 1027 1028 The name or code of the country. 1029 """ 1030 _qname = GDATA_TEMPLATE % 'country' 1031 1032 1033class FormattedAddress(atom.core.XmlElement): 1034 """The gd:formattedAddress element. 1035 1036 The full, unstructured postal address. 1037 """ 1038 _qname = GDATA_TEMPLATE % 'formattedAddress' 1039 1040 1041class StructuredPostalAddress(atom.core.XmlElement): 1042 """The gd:structuredPostalAddress element. 1043 1044 Postal address split into components. It allows to store the address 1045 in locale independent format. The fields can be interpreted and used 1046 to generate formatted, locale dependent address. The following elements 1047 reperesent parts of the address: agent, house name, street, P.O. box, 1048 neighborhood, city, subregion, region, postal code, country. The 1049 subregion element is not used for postal addresses, it is provided for 1050 extended uses of addresses only. In order to store postal address in an 1051 unstructured form formatted address field is provided. 1052 """ 1053 _qname = GDATA_TEMPLATE % 'structuredPostalAddress' 1054 rel = 'rel' 1055 mail_class = 'mailClass' 1056 usage = 'usage' 1057 label = 'label' 1058 primary = 'primary' 1059 agent = Agent 1060 house_name = HouseName 1061 street = Street 1062 po_box = PoBox 1063 neighborhood = Neighborhood 1064 city = City 1065 subregion = Subregion 1066 region = Region 1067 postcode = Postcode 1068 country = Country 1069 formatted_address = FormattedAddress 1070 1071 1072class Where(atom.core.XmlElement): 1073 """The gd:where element. 1074 1075 A place (such as an event location) associated with the containing 1076 entity. The type of the association is determined by the rel attribute; 1077 the details of the location are contained in an embedded or linked-to 1078 Contact entry. 1079 1080 A <gd:where> element is more general than a <gd:geoPt> element. The 1081 former identifies a place using a text description and/or a Contact 1082 entry, while the latter identifies a place using a specific geographic 1083 location. 1084 """ 1085 _qname = GDATA_TEMPLATE % 'where' 1086 label = 'label' 1087 rel = 'rel' 1088 value = 'valueString' 1089 entry_link = EntryLink 1090 1091 1092class AttendeeType(atom.core.XmlElement): 1093 """The gd:attendeeType element.""" 1094 _qname = GDATA_TEMPLATE % 'attendeeType' 1095 value = 'value' 1096 1097 1098class AttendeeStatus(atom.core.XmlElement): 1099 """The gd:attendeeStatus element.""" 1100 _qname = GDATA_TEMPLATE % 'attendeeStatus' 1101 value = 'value' 1102 1103 1104class Who(atom.core.XmlElement): 1105 """The gd:who element. 1106 1107 A person associated with the containing entity. The type of the 1108 association is determined by the rel attribute; the details about the 1109 person are contained in an embedded or linked-to Contact entry. 1110 1111 The <gd:who> element can be used to specify email senders and 1112 recipients, calendar event organizers, and so on. 1113 """ 1114 _qname = GDATA_TEMPLATE % 'who' 1115 email = 'email' 1116 rel = 'rel' 1117 value = 'valueString' 1118 attendee_status = AttendeeStatus 1119 attendee_type = AttendeeType 1120 entry_link = EntryLink 1121 1122 1123class Deleted(atom.core.XmlElement): 1124 """gd:deleted when present, indicates the containing entry is deleted.""" 1125 _qname = GD_TEMPLATE % 'deleted' 1126 1127 1128class Money(atom.core.XmlElement): 1129 """Describes money""" 1130 _qname = GD_TEMPLATE % 'money' 1131 amount = 'amount' 1132 currency_code = 'currencyCode' 1133 1134 1135class MediaSource(object): 1136 """GData Entries can refer to media sources, so this class provides a 1137 place to store references to these objects along with some metadata. 1138 """ 1139 1140 def __init__(self, file_handle=None, content_type=None, content_length=None, 1141 file_path=None, file_name=None): 1142 """Creates an object of type MediaSource. 1143 1144 Args: 1145 file_handle: A file handle pointing to the file to be encapsulated in the 1146 MediaSource. 1147 content_type: string The MIME type of the file. Required if a file_handle 1148 is given. 1149 content_length: int The size of the file. Required if a file_handle is 1150 given. 1151 file_path: string (optional) A full path name to the file. Used in 1152 place of a file_handle. 1153 file_name: string The name of the file without any path information. 1154 Required if a file_handle is given. 1155 """ 1156 self.file_handle = file_handle 1157 self.content_type = content_type 1158 self.content_length = content_length 1159 self.file_name = file_name 1160 1161 if (file_handle is None and content_type is not None and 1162 file_path is not None): 1163 self.set_file_handle(file_path, content_type) 1164 1165 def set_file_handle(self, file_name, content_type): 1166 """A helper function which can create a file handle from a given filename 1167 and set the content type and length all at once. 1168 1169 Args: 1170 file_name: string The path and file name to the file containing the media 1171 content_type: string A MIME type representing the type of the media 1172 """ 1173 1174 self.file_handle = open(file_name, 'rb') 1175 self.content_type = content_type 1176 self.content_length = os.path.getsize(file_name) 1177 self.file_name = os.path.basename(file_name) 1178 1179 SetFileHandle = set_file_handle 1180 1181 def modify_request(self, http_request): 1182 http_request.add_body_part(self.file_handle, self.content_type, 1183 self.content_length) 1184 return http_request 1185 1186 ModifyRequest = modify_request