PageRenderTime 58ms CodeModel.GetById 17ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/gdata/data.py

http://radioappz.googlecode.com/
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