PageRenderTime 45ms CodeModel.GetById 3ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/gdata/__init__.py

http://radioappz.googlecode.com/
Python | 835 lines | 770 code | 20 blank | 45 comment | 4 complexity | 5dcaa4ad36cb319798ea710077c41115 MD5 | raw file
  1#!/usr/bin/python
  2#
  3# Copyright (C) 2006 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"""Contains classes representing Google Data elements.
 19
 20  Extends Atom classes to add Google Data specific elements.
 21"""
 22
 23
 24__author__ = 'j.s@google.com (Jeffrey Scudder)'
 25
 26import os
 27import atom
 28try:
 29  from xml.etree import cElementTree as ElementTree
 30except ImportError:
 31  try:
 32    import cElementTree as ElementTree
 33  except ImportError:
 34    try:
 35      from xml.etree import ElementTree
 36    except ImportError:
 37      from elementtree import ElementTree
 38
 39
 40# XML namespaces which are often used in GData entities.
 41GDATA_NAMESPACE = 'http://schemas.google.com/g/2005'
 42GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s'
 43OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/'
 44OPENSEARCH_TEMPLATE = '{http://a9.com/-/spec/opensearchrss/1.0/}%s'
 45BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch'
 46GACL_NAMESPACE = 'http://schemas.google.com/acl/2007'
 47GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s'
 48
 49
 50# Labels used in batch request entries to specify the desired CRUD operation.
 51BATCH_INSERT = 'insert'
 52BATCH_UPDATE = 'update'
 53BATCH_DELETE = 'delete'
 54BATCH_QUERY = 'query'
 55
 56class Error(Exception):
 57  pass
 58
 59
 60class MissingRequiredParameters(Error):
 61  pass
 62
 63
 64class MediaSource(object):
 65  """GData Entries can refer to media sources, so this class provides a
 66  place to store references to these objects along with some metadata.
 67  """
 68
 69  def __init__(self, file_handle=None, content_type=None, content_length=None,
 70      file_path=None, file_name=None):
 71    """Creates an object of type MediaSource.
 72
 73    Args:
 74      file_handle: A file handle pointing to the file to be encapsulated in the
 75                   MediaSource
 76      content_type: string The MIME type of the file. Required if a file_handle
 77                    is given.
 78      content_length: int The size of the file. Required if a file_handle is
 79                      given.
 80      file_path: string (optional) A full path name to the file. Used in
 81                    place of a file_handle.
 82      file_name: string The name of the file without any path information.
 83                 Required if a file_handle is given.
 84    """
 85    self.file_handle = file_handle
 86    self.content_type = content_type
 87    self.content_length = content_length
 88    self.file_name = file_name
 89
 90    if (file_handle is None and content_type is not None and
 91        file_path is not None):
 92      self.setFile(file_path, content_type)
 93
 94  def setFile(self, file_name, content_type):
 95    """A helper function which can create a file handle from a given filename
 96    and set the content type and length all at once.
 97
 98    Args:
 99      file_name: string The path and file name to the file containing the media
100      content_type: string A MIME type representing the type of the media
101    """
102
103    self.file_handle = open(file_name, 'rb')
104    self.content_type = content_type
105    self.content_length = os.path.getsize(file_name)
106    self.file_name = os.path.basename(file_name)
107
108
109class LinkFinder(atom.LinkFinder):
110  """An "interface" providing methods to find link elements
111
112  GData Entry elements often contain multiple links which differ in the rel
113  attribute or content type. Often, developers are interested in a specific
114  type of link so this class provides methods to find specific classes of
115  links.
116
117  This class is used as a mixin in GData entries.
118  """
119
120  def GetSelfLink(self):
121    """Find the first link with rel set to 'self'
122
123    Returns:
124      An atom.Link or none if none of the links had rel equal to 'self'
125    """
126
127    for a_link in self.link:
128      if a_link.rel == 'self':
129        return a_link
130    return None
131
132  def GetEditLink(self):
133    for a_link in self.link:
134      if a_link.rel == 'edit':
135        return a_link
136    return None
137
138  def GetEditMediaLink(self):
139    """The Picasa API mistakenly returns media-edit rather than edit-media, but
140    this may change soon.
141    """
142    for a_link in self.link:
143      if a_link.rel == 'edit-media':
144        return a_link
145      if a_link.rel == 'media-edit':
146        return a_link
147    return None
148
149  def GetHtmlLink(self):
150    """Find the first link with rel of alternate and type of text/html
151
152    Returns:
153      An atom.Link or None if no links matched
154    """
155    for a_link in self.link:
156      if a_link.rel == 'alternate' and a_link.type == 'text/html':
157        return a_link
158    return None
159
160  def GetPostLink(self):
161    """Get a link containing the POST target URL.
162
163    The POST target URL is used to insert new entries.
164
165    Returns:
166      A link object with a rel matching the POST type.
167    """
168    for a_link in self.link:
169      if a_link.rel == 'http://schemas.google.com/g/2005#post':
170        return a_link
171    return None
172
173  def GetAclLink(self):
174    for a_link in self.link:
175      if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList':
176        return a_link
177    return None
178
179  def GetFeedLink(self):
180    for a_link in self.link:
181      if a_link.rel == 'http://schemas.google.com/g/2005#feed':
182        return a_link
183    return None
184
185  def GetNextLink(self):
186    for a_link in self.link:
187      if a_link.rel == 'next':
188        return a_link
189    return None
190
191  def GetPrevLink(self):
192    for a_link in self.link:
193      if a_link.rel == 'previous':
194        return a_link
195    return None
196
197
198class TotalResults(atom.AtomBase):
199  """opensearch:TotalResults for a GData feed"""
200
201  _tag = 'totalResults'
202  _namespace = OPENSEARCH_NAMESPACE
203  _children = atom.AtomBase._children.copy()
204  _attributes = atom.AtomBase._attributes.copy()
205
206  def __init__(self, extension_elements=None,
207     extension_attributes=None, text=None):
208    self.text = text
209    self.extension_elements = extension_elements or []
210    self.extension_attributes = extension_attributes or {}
211
212
213def TotalResultsFromString(xml_string):
214  return atom.CreateClassFromXMLString(TotalResults, xml_string)
215
216
217class StartIndex(atom.AtomBase):
218  """The opensearch:startIndex element in GData feed"""
219
220  _tag = 'startIndex'
221  _namespace = OPENSEARCH_NAMESPACE
222  _children = atom.AtomBase._children.copy()
223  _attributes = atom.AtomBase._attributes.copy()
224
225  def __init__(self, extension_elements=None,
226      extension_attributes=None, text=None):
227    self.text = text
228    self.extension_elements = extension_elements or []
229    self.extension_attributes = extension_attributes or {}
230
231
232def StartIndexFromString(xml_string):
233  return atom.CreateClassFromXMLString(StartIndex, xml_string)
234
235
236class ItemsPerPage(atom.AtomBase):
237  """The opensearch:itemsPerPage element in GData feed"""
238
239  _tag = 'itemsPerPage'
240  _namespace = OPENSEARCH_NAMESPACE
241  _children = atom.AtomBase._children.copy()
242  _attributes = atom.AtomBase._attributes.copy()
243
244  def __init__(self, extension_elements=None,
245      extension_attributes=None, text=None):
246    self.text = text
247    self.extension_elements = extension_elements or []
248    self.extension_attributes = extension_attributes or {}
249
250
251def ItemsPerPageFromString(xml_string):
252  return atom.CreateClassFromXMLString(ItemsPerPage, xml_string)
253
254
255class ExtendedProperty(atom.AtomBase):
256  """The Google Data extendedProperty element.
257
258  Used to store arbitrary key-value information specific to your
259  application. The value can either be a text string stored as an XML
260  attribute (.value), or an XML node (XmlBlob) as a child element.
261
262  This element is used in the Google Calendar data API and the Google
263  Contacts data API.
264  """
265
266  _tag = 'extendedProperty'
267  _namespace = GDATA_NAMESPACE
268  _children = atom.AtomBase._children.copy()
269  _attributes = atom.AtomBase._attributes.copy()
270  _attributes['name'] = 'name'
271  _attributes['value'] = 'value'
272
273  def __init__(self, name=None, value=None, extension_elements=None,
274      extension_attributes=None, text=None):
275    self.name = name
276    self.value = value
277    self.text = text
278    self.extension_elements = extension_elements or []
279    self.extension_attributes = extension_attributes or {}
280
281  def GetXmlBlobExtensionElement(self):
282    """Returns the XML blob as an atom.ExtensionElement.
283
284    Returns:
285      An atom.ExtensionElement representing the blob's XML, or None if no
286      blob was set.
287    """
288    if len(self.extension_elements) < 1:
289      return None
290    else:
291      return self.extension_elements[0]
292
293  def GetXmlBlobString(self):
294    """Returns the XML blob as a string.
295
296    Returns:
297      A string containing the blob's XML, or None if no blob was set.
298    """
299    blob = self.GetXmlBlobExtensionElement()
300    if blob:
301      return blob.ToString()
302    return None
303
304  def SetXmlBlob(self, blob):
305    """Sets the contents of the extendedProperty to XML as a child node.
306
307    Since the extendedProperty is only allowed one child element as an XML
308    blob, setting the XML blob will erase any preexisting extension elements
309    in this object.
310
311    Args:
312      blob: str, ElementTree Element or atom.ExtensionElement representing
313            the XML blob stored in the extendedProperty.
314    """
315    # Erase any existing extension_elements, clears the child nodes from the
316    # extendedProperty.
317    self.extension_elements = []
318    if isinstance(blob, atom.ExtensionElement):
319      self.extension_elements.append(blob)
320    elif ElementTree.iselement(blob):
321      self.extension_elements.append(atom._ExtensionElementFromElementTree(
322          blob))
323    else:
324      self.extension_elements.append(atom.ExtensionElementFromString(blob))
325
326
327def ExtendedPropertyFromString(xml_string):
328  return atom.CreateClassFromXMLString(ExtendedProperty, xml_string)
329
330
331class GDataEntry(atom.Entry, LinkFinder):
332  """Extends Atom Entry to provide data processing"""
333
334  _tag = atom.Entry._tag
335  _namespace = atom.Entry._namespace
336  _children = atom.Entry._children.copy()
337  _attributes = atom.Entry._attributes.copy()
338
339  def __GetId(self):
340    return self.__id
341
342  # This method was created to strip the unwanted whitespace from the id's
343  # text node.
344  def __SetId(self, id):
345    self.__id = id
346    if id is not None and id.text is not None:
347      self.__id.text = id.text.strip()
348
349  id = property(__GetId, __SetId)
350
351  def IsMedia(self):
352    """Determines whether or not an entry is a GData Media entry.
353    """
354    if (self.GetEditMediaLink()):
355      return True
356    else:
357      return False
358
359  def GetMediaURL(self):
360    """Returns the URL to the media content, if the entry is a media entry.
361    Otherwise returns None.
362    """
363    if not self.IsMedia():
364      return None
365    else:
366      return self.content.src
367
368
369def GDataEntryFromString(xml_string):
370  """Creates a new GDataEntry instance given a string of XML."""
371  return atom.CreateClassFromXMLString(GDataEntry, xml_string)
372
373
374class GDataFeed(atom.Feed, LinkFinder):
375  """A Feed from a GData service"""
376
377  _tag = 'feed'
378  _namespace = atom.ATOM_NAMESPACE
379  _children = atom.Feed._children.copy()
380  _attributes = atom.Feed._attributes.copy()
381  _children['{%s}totalResults' % OPENSEARCH_NAMESPACE] = ('total_results',
382                                                          TotalResults)
383  _children['{%s}startIndex' % OPENSEARCH_NAMESPACE] = ('start_index',
384                                                        StartIndex)
385  _children['{%s}itemsPerPage' % OPENSEARCH_NAMESPACE] = ('items_per_page',
386                                                          ItemsPerPage)
387               # Add a conversion rule for atom:entry to make it into a GData
388               # Entry.
389  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GDataEntry])
390
391  def __GetId(self):
392    return self.__id
393
394  def __SetId(self, id):
395    self.__id = id
396    if id is not None and id.text is not None:
397      self.__id.text = id.text.strip()
398
399  id = property(__GetId, __SetId)
400
401  def __GetGenerator(self):
402    return self.__generator
403
404  def __SetGenerator(self, generator):
405    self.__generator = generator
406    if generator is not None:
407      self.__generator.text = generator.text.strip()
408
409  generator = property(__GetGenerator, __SetGenerator)
410
411  def __init__(self, author=None, category=None, contributor=None,
412      generator=None, icon=None, atom_id=None, link=None, logo=None,
413      rights=None, subtitle=None, title=None, updated=None, entry=None,
414      total_results=None, start_index=None, items_per_page=None,
415      extension_elements=None, extension_attributes=None, text=None):
416    """Constructor for Source
417
418    Args:
419      author: list (optional) A list of Author instances which belong to this
420          class.
421      category: list (optional) A list of Category instances
422      contributor: list (optional) A list on Contributor instances
423      generator: Generator (optional)
424      icon: Icon (optional)
425      id: Id (optional) The entry's Id element
426      link: list (optional) A list of Link instances
427      logo: Logo (optional)
428      rights: Rights (optional) The entry's Rights element
429      subtitle: Subtitle (optional) The entry's subtitle element
430      title: Title (optional) the entry's title element
431      updated: Updated (optional) the entry's updated element
432      entry: list (optional) A list of the Entry instances contained in the
433          feed.
434      text: String (optional) The text contents of the element. This is the
435          contents of the Entry's XML text node.
436          (Example: <foo>This is the text</foo>)
437      extension_elements: list (optional) A list of ExtensionElement instances
438          which are children of this element.
439      extension_attributes: dict (optional) A dictionary of strings which are
440          the values for additional XML attributes of this element.
441    """
442
443    self.author = author or []
444    self.category = category or []
445    self.contributor = contributor or []
446    self.generator = generator
447    self.icon = icon
448    self.id = atom_id
449    self.link = link or []
450    self.logo = logo
451    self.rights = rights
452    self.subtitle = subtitle
453    self.title = title
454    self.updated = updated
455    self.entry = entry or []
456    self.total_results = total_results
457    self.start_index = start_index
458    self.items_per_page = items_per_page
459    self.text = text
460    self.extension_elements = extension_elements or []
461    self.extension_attributes = extension_attributes or {}
462
463
464def GDataFeedFromString(xml_string):
465  return atom.CreateClassFromXMLString(GDataFeed, xml_string)
466
467
468class BatchId(atom.AtomBase):
469  _tag = 'id'
470  _namespace = BATCH_NAMESPACE
471  _children = atom.AtomBase._children.copy()
472  _attributes = atom.AtomBase._attributes.copy()
473
474
475def BatchIdFromString(xml_string):
476  return atom.CreateClassFromXMLString(BatchId, xml_string)
477
478
479class BatchOperation(atom.AtomBase):
480  _tag = 'operation'
481  _namespace = BATCH_NAMESPACE
482  _children = atom.AtomBase._children.copy()
483  _attributes = atom.AtomBase._attributes.copy()
484  _attributes['type'] = 'type'
485
486  def __init__(self, op_type=None, extension_elements=None,
487               extension_attributes=None,
488               text=None):
489    self.type = op_type
490    atom.AtomBase.__init__(self,
491                           extension_elements=extension_elements,
492                           extension_attributes=extension_attributes,
493                           text=text)
494
495
496def BatchOperationFromString(xml_string):
497  return atom.CreateClassFromXMLString(BatchOperation, xml_string)
498
499
500class BatchStatus(atom.AtomBase):
501  """The batch:status element present in a batch response entry.
502
503  A status element contains the code (HTTP response code) and
504  reason as elements. In a single request these fields would
505  be part of the HTTP response, but in a batch request each
506  Entry operation has a corresponding Entry in the response
507  feed which includes status information.
508
509  See http://code.google.com/apis/gdata/batch.html#Handling_Errors
510  """
511
512  _tag = 'status'
513  _namespace = BATCH_NAMESPACE
514  _children = atom.AtomBase._children.copy()
515  _attributes = atom.AtomBase._attributes.copy()
516  _attributes['code'] = 'code'
517  _attributes['reason'] = 'reason'
518  _attributes['content-type'] = 'content_type'
519
520  def __init__(self, code=None, reason=None, content_type=None,
521               extension_elements=None, extension_attributes=None, text=None):
522    self.code = code
523    self.reason = reason
524    self.content_type = content_type
525    atom.AtomBase.__init__(self, extension_elements=extension_elements,
526                           extension_attributes=extension_attributes,
527                           text=text)
528
529
530def BatchStatusFromString(xml_string):
531  return atom.CreateClassFromXMLString(BatchStatus, xml_string)
532
533
534class BatchEntry(GDataEntry):
535  """An atom:entry for use in batch requests.
536
537  The BatchEntry contains additional members to specify the operation to be
538  performed on this entry and a batch ID so that the server can reference
539  individual operations in the response feed. For more information, see:
540  http://code.google.com/apis/gdata/batch.html
541  """
542
543  _tag = GDataEntry._tag
544  _namespace = GDataEntry._namespace
545  _children = GDataEntry._children.copy()
546  _children['{%s}operation' % BATCH_NAMESPACE] = ('batch_operation', BatchOperation)
547  _children['{%s}id' % BATCH_NAMESPACE] = ('batch_id', BatchId)
548  _children['{%s}status' % BATCH_NAMESPACE] = ('batch_status', BatchStatus)
549  _attributes = GDataEntry._attributes.copy()
550
551  def __init__(self, author=None, category=None, content=None,
552      contributor=None, atom_id=None, link=None, published=None, rights=None,
553      source=None, summary=None, control=None, title=None, updated=None,
554      batch_operation=None, batch_id=None, batch_status=None,
555      extension_elements=None, extension_attributes=None, text=None):
556    self.batch_operation = batch_operation
557    self.batch_id = batch_id
558    self.batch_status = batch_status
559    GDataEntry.__init__(self, author=author, category=category,
560        content=content, contributor=contributor, atom_id=atom_id, link=link,
561        published=published, rights=rights, source=source, summary=summary,
562        control=control, title=title, updated=updated,
563        extension_elements=extension_elements,
564        extension_attributes=extension_attributes, text=text)
565
566
567def BatchEntryFromString(xml_string):
568  return atom.CreateClassFromXMLString(BatchEntry, xml_string)
569
570
571class BatchInterrupted(atom.AtomBase):
572  """The batch:interrupted element sent if batch request was interrupted.
573
574  Only appears in a feed if some of the batch entries could not be processed.
575  See: http://code.google.com/apis/gdata/batch.html#Handling_Errors
576  """
577
578  _tag = 'interrupted'
579  _namespace = BATCH_NAMESPACE
580  _children = atom.AtomBase._children.copy()
581  _attributes = atom.AtomBase._attributes.copy()
582  _attributes['reason'] = 'reason'
583  _attributes['success'] = 'success'
584  _attributes['failures'] = 'failures'
585  _attributes['parsed'] = 'parsed'
586
587  def __init__(self, reason=None, success=None, failures=None, parsed=None,
588               extension_elements=None, extension_attributes=None, text=None):
589    self.reason = reason
590    self.success = success
591    self.failures = failures
592    self.parsed = parsed
593    atom.AtomBase.__init__(self, extension_elements=extension_elements,
594                           extension_attributes=extension_attributes,
595                           text=text)
596
597
598def BatchInterruptedFromString(xml_string):
599  return atom.CreateClassFromXMLString(BatchInterrupted, xml_string)
600
601
602class BatchFeed(GDataFeed):
603  """A feed containing a list of batch request entries."""
604
605  _tag = GDataFeed._tag
606  _namespace = GDataFeed._namespace
607  _children = GDataFeed._children.copy()
608  _attributes = GDataFeed._attributes.copy()
609  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchEntry])
610  _children['{%s}interrupted' % BATCH_NAMESPACE] = ('interrupted', BatchInterrupted)
611
612  def __init__(self, author=None, category=None, contributor=None,
613      generator=None, icon=None, atom_id=None, link=None, logo=None,
614      rights=None, subtitle=None, title=None, updated=None, entry=None,
615      total_results=None, start_index=None, items_per_page=None,
616      interrupted=None,
617      extension_elements=None, extension_attributes=None, text=None):
618    self.interrupted = interrupted
619    GDataFeed.__init__(self, author=author, category=category,
620                       contributor=contributor, generator=generator,
621                       icon=icon, atom_id=atom_id, link=link,
622                       logo=logo, rights=rights, subtitle=subtitle,
623                       title=title, updated=updated, entry=entry,
624                       total_results=total_results, start_index=start_index,
625                       items_per_page=items_per_page,
626                       extension_elements=extension_elements,
627                       extension_attributes=extension_attributes,
628                       text=text)
629
630  def AddBatchEntry(self, entry=None, id_url_string=None,
631                     batch_id_string=None, operation_string=None):
632    """Logic for populating members of a BatchEntry and adding to the feed.
633
634
635    If the entry is not a BatchEntry, it is converted to a BatchEntry so
636    that the batch specific members will be present.
637
638    The id_url_string can be used in place of an entry if the batch operation
639    applies to a URL. For example query and delete operations require just
640    the URL of an entry, no body is sent in the HTTP request. If an
641    id_url_string is sent instead of an entry, a BatchEntry is created and
642    added to the feed.
643
644    This method also assigns the desired batch id to the entry so that it
645    can be referenced in the server's response. If the batch_id_string is
646    None, this method will assign a batch_id to be the index at which this
647    entry will be in the feed's entry list.
648
649    Args:
650      entry: BatchEntry, atom.Entry, or another Entry flavor (optional) The
651          entry which will be sent to the server as part of the batch request.
652          The item must have a valid atom id so that the server knows which
653          entry this request references.
654      id_url_string: str (optional) The URL of the entry to be acted on. You
655          can find this URL in the text member of the atom id for an entry.
656          If an entry is not sent, this id will be used to construct a new
657          BatchEntry which will be added to the request feed.
658      batch_id_string: str (optional) The batch ID to be used to reference
659          this batch operation in the results feed. If this parameter is None,
660          the current length of the feed's entry array will be used as a
661          count. Note that batch_ids should either always be specified or
662          never, mixing could potentially result in duplicate batch ids.
663      operation_string: str (optional) The desired batch operation which will
664          set the batch_operation.type member of the entry. Options are
665          'insert', 'update', 'delete', and 'query'
666
667    Raises:
668      MissingRequiredParameters: Raised if neither an id_ url_string nor an
669          entry are provided in the request.
670
671    Returns:
672      The added entry.
673    """
674    if entry is None and id_url_string is None:
675      raise MissingRequiredParameters('supply either an entry or URL string')
676    if entry is None and id_url_string is not None:
677      entry = BatchEntry(atom_id=atom.Id(text=id_url_string))
678    # TODO: handle cases in which the entry lacks batch_... members.
679    #if not isinstance(entry, BatchEntry):
680      # Convert the entry to a batch entry.
681    if batch_id_string is not None:
682      entry.batch_id = BatchId(text=batch_id_string)
683    elif entry.batch_id is None or entry.batch_id.text is None:
684      entry.batch_id = BatchId(text=str(len(self.entry)))
685    if operation_string is not None:
686      entry.batch_operation = BatchOperation(op_type=operation_string)
687    self.entry.append(entry)
688    return entry
689
690  def AddInsert(self, entry, batch_id_string=None):
691    """Add an insert request to the operations in this batch request feed.
692
693    If the entry doesn't yet have an operation or a batch id, these will
694    be set to the insert operation and a batch_id specified as a parameter.
695
696    Args:
697      entry: BatchEntry The entry which will be sent in the batch feed as an
698          insert request.
699      batch_id_string: str (optional) The batch ID to be used to reference
700          this batch operation in the results feed. If this parameter is None,
701          the current length of the feed's entry array will be used as a
702          count. Note that batch_ids should either always be specified or
703          never, mixing could potentially result in duplicate batch ids.
704    """
705    entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string,
706                               operation_string=BATCH_INSERT)
707
708  def AddUpdate(self, entry, batch_id_string=None):
709    """Add an update request to the list of batch operations in this feed.
710
711    Sets the operation type of the entry to insert if it is not already set
712    and assigns the desired batch id to the entry so that it can be
713    referenced in the server's response.
714
715    Args:
716      entry: BatchEntry The entry which will be sent to the server as an
717          update (HTTP PUT) request. The item must have a valid atom id
718          so that the server knows which entry to replace.
719      batch_id_string: str (optional) The batch ID to be used to reference
720          this batch operation in the results feed. If this parameter is None,
721          the current length of the feed's entry array will be used as a
722          count. See also comments for AddInsert.
723    """
724    entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string,
725                               operation_string=BATCH_UPDATE)
726
727  def AddDelete(self, url_string=None, entry=None, batch_id_string=None):
728    """Adds a delete request to the batch request feed.
729
730    This method takes either the url_string which is the atom id of the item
731    to be deleted, or the entry itself. The atom id of the entry must be
732    present so that the server knows which entry should be deleted.
733
734    Args:
735      url_string: str (optional) The URL of the entry to be deleted. You can
736         find this URL in the text member of the atom id for an entry.
737      entry: BatchEntry (optional) The entry to be deleted.
738      batch_id_string: str (optional)
739
740    Raises:
741      MissingRequiredParameters: Raised if neither a url_string nor an entry
742          are provided in the request.
743    """
744    entry = self.AddBatchEntry(entry=entry, id_url_string=url_string,
745                               batch_id_string=batch_id_string,
746                               operation_string=BATCH_DELETE)
747
748  def AddQuery(self, url_string=None, entry=None, batch_id_string=None):
749    """Adds a query request to the batch request feed.
750
751    This method takes either the url_string which is the query URL
752    whose results will be added to the result feed. The query URL will
753    be encapsulated in a BatchEntry, and you may pass in the BatchEntry
754    with a query URL instead of sending a url_string.
755
756    Args:
757      url_string: str (optional)
758      entry: BatchEntry (optional)
759      batch_id_string: str (optional)
760
761    Raises:
762      MissingRequiredParameters
763    """
764    entry = self.AddBatchEntry(entry=entry, id_url_string=url_string,
765                               batch_id_string=batch_id_string,
766                               operation_string=BATCH_QUERY)
767
768  def GetBatchLink(self):
769    for link in self.link:
770      if link.rel == 'http://schemas.google.com/g/2005#batch':
771        return link
772    return None
773
774
775def BatchFeedFromString(xml_string):
776  return atom.CreateClassFromXMLString(BatchFeed, xml_string)
777
778
779class EntryLink(atom.AtomBase):
780  """The gd:entryLink element"""
781
782  _tag = 'entryLink'
783  _namespace = GDATA_NAMESPACE
784  _children = atom.AtomBase._children.copy()
785  _attributes = atom.AtomBase._attributes.copy()
786  # The entry used to be an atom.Entry, now it is a GDataEntry.
787  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry)
788  _attributes['rel'] = 'rel'
789  _attributes['readOnly'] = 'read_only'
790  _attributes['href'] = 'href'
791
792  def __init__(self, href=None, read_only=None, rel=None,
793      entry=None, extension_elements=None,
794      extension_attributes=None, text=None):
795    self.href = href
796    self.read_only = read_only
797    self.rel = rel
798    self.entry = entry
799    self.text = text
800    self.extension_elements = extension_elements or []
801    self.extension_attributes = extension_attributes or {}
802
803
804def EntryLinkFromString(xml_string):
805  return atom.CreateClassFromXMLString(EntryLink, xml_string)
806
807
808class FeedLink(atom.AtomBase):
809  """The gd:feedLink element"""
810
811  _tag = 'feedLink'
812  _namespace = GDATA_NAMESPACE
813  _children = atom.AtomBase._children.copy()
814  _attributes = atom.AtomBase._attributes.copy()
815  _children['{%s}feed' % atom.ATOM_NAMESPACE] = ('feed', GDataFeed)
816  _attributes['rel'] = 'rel'
817  _attributes['readOnly'] = 'read_only'
818  _attributes['countHint'] = 'count_hint'
819  _attributes['href'] = 'href'
820
821  def __init__(self, count_hint=None, href=None, read_only=None, rel=None,
822      feed=None, extension_elements=None, extension_attributes=None,
823      text=None):
824    self.count_hint = count_hint
825    self.href = href
826    self.read_only = read_only
827    self.rel = rel
828    self.feed = feed
829    self.text = text
830    self.extension_elements = extension_elements or []
831    self.extension_attributes = extension_attributes or {}
832
833
834def FeedLinkFromString(xml_string):
835  return atom.CreateClassFromXMLString(FeedLink, xml_string)