thirdparty/google_appengine/google/appengine/api/datastore_entities.py
changeset 109 620f9b141567
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/api/datastore_entities.py	Tue Aug 26 21:49:54 2008 +0000
@@ -0,0 +1,343 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Classes for common kinds, including Contact, Message, and Event.
+
+Most of these kinds are based on the gd namespace "kinds" from GData:
+
+  http://code.google.com/apis/gdata/common-elements.html
+"""
+
+
+
+
+
+import types
+import urlparse
+from xml.sax import saxutils
+from google.appengine.datastore import datastore_pb
+from google.appengine.api import datastore
+from google.appengine.api import datastore_errors
+from google.appengine.api import datastore_types
+
+class GdKind(datastore.Entity):
+  """ A base class for gd namespace kinds.
+
+  This class contains common logic for all gd namespace kinds. For example,
+  this class translates datastore (app id, kind, key) tuples to tag:
+  URIs appropriate for use in <key> tags.
+  """
+
+  HEADER = u"""<entry xmlns:gd='http://schemas.google.com/g/2005'>
+  <category scheme='http://schemas.google.com/g/2005#kind'
+            term='http://schemas.google.com/g/2005#%s' />"""
+  FOOTER = u"""
+</entry>"""
+
+  _kind_properties = set()
+  _contact_properties = set()
+
+  def __init__(self, kind, title, kind_properties, contact_properties=[]):
+    """ Ctor.
+
+    title is the name of this particular entity, e.g. Bob Jones or Mom's
+    Birthday Party.
+
+    kind_properties is a list of property names that should be included in
+    this entity's XML encoding as first-class XML elements, instead of
+    <property> elements. 'title' and 'content' are added to kind_properties
+    automatically, and may not appear in contact_properties.
+
+    contact_properties is a list of property names that are Keys that point to
+    Contact entities, and should be included in this entity's XML encoding as
+    <gd:who> elements. If a property name is included in both kind_properties
+    and contact_properties, it is treated as a Contact property.
+
+    Args:
+    kind: string
+    title: string
+    kind_properties: list of strings
+    contact_properties: list of string
+    """
+    datastore.Entity.__init__(self, kind)
+
+    if not isinstance(title, types.StringTypes):
+      raise datastore_errors.BadValueError(
+        'Expected a string for title; received %s (a %s).' %
+        (title, datastore_types.typename(title)))
+    self['title'] = title
+    self['content'] = ''
+
+    self._contact_properties = set(contact_properties)
+    assert not self._contact_properties.intersection(self.keys())
+
+    self._kind_properties = set(kind_properties) - self._contact_properties
+    self._kind_properties.add('title')
+    self._kind_properties.add('content')
+
+  def _KindPropertiesToXml(self):
+    """ Convert the properties that are part of this gd kind to XML. For
+    testability, the XML elements in the output are sorted alphabetically
+    by property name.
+
+    Returns:
+    string  # the XML representation of the gd kind properties
+    """
+    properties = self._kind_properties.intersection(set(self.keys()))
+
+    xml = u''
+    for prop in sorted(properties):
+      prop_xml = saxutils.quoteattr(prop)[1:-1]
+
+      value = self[prop]
+      has_toxml = (hasattr(value, 'ToXml') or
+                   isinstance(value, list) and hasattr(value[0], 'ToXml'))
+
+      for val in self._XmlEscapeValues(prop):
+        if has_toxml:
+          xml += '\n  %s' % val
+        else:
+          xml += '\n  <%s>%s</%s>' % (prop_xml, val, prop_xml)
+
+    return xml
+
+
+  def _ContactPropertiesToXml(self):
+    """ Convert this kind's Contact properties kind to XML. For testability,
+    the XML elements in the output are sorted alphabetically by property name.
+
+    Returns:
+    string  # the XML representation of the Contact properties
+    """
+    properties = self._contact_properties.intersection(set(self.keys()))
+
+    xml = u''
+    for prop in sorted(properties):
+      values = self[prop]
+      if not isinstance(values, list):
+        values = [values]
+
+      for value in values:
+        assert isinstance(value, datastore_types.Key)
+        xml += """
+  <gd:who rel="http://schemas.google.com/g/2005#%s.%s>
+    <gd:entryLink href="%s" />
+  </gd:who>""" % (self.kind().lower(), prop, value.ToTagUri())
+
+    return xml
+
+
+  def _LeftoverPropertiesToXml(self):
+    """ Convert all of this entity's properties that *aren't* part of this gd
+    kind to XML.
+
+    Returns:
+    string  # the XML representation of the leftover properties
+    """
+    leftovers = set(self.keys())
+    leftovers -= self._kind_properties
+    leftovers -= self._contact_properties
+    if leftovers:
+      return u'\n  ' + '\n  '.join(self._PropertiesToXml(leftovers))
+    else:
+      return u''
+
+  def ToXml(self):
+    """ Returns an XML representation of this entity, as a string.
+    """
+    xml = GdKind.HEADER % self.kind().lower()
+    xml += self._KindPropertiesToXml()
+    xml += self._ContactPropertiesToXml()
+    xml += self._LeftoverPropertiesToXml()
+    xml += GdKind.FOOTER
+    return xml
+
+
+class Message(GdKind):
+  """A message, such as an email, a discussion group posting, or a comment.
+
+  Includes the message title, contents, participants, and other properties.
+
+  This is the gd Message kind. See:
+  http://code.google.com/apis/gdata/common-elements.html#gdMessageKind
+
+  These properties are meaningful. They are all optional.
+
+  property name  property type    meaning
+  -------------------------------------
+  title          string         message subject
+  content        string         message body
+  from           Contact*       sender
+  to             Contact*       primary recipient
+  cc             Contact*       CC recipient
+  bcc            Contact*       BCC recipient
+  reply-to       Contact*       intended recipient of replies
+  link           Link*          attachment
+  category       Category*      tag or label associated with this message
+  geoPt          GeoPt*         geographic location the message was posted from
+  rating         Rating*        message rating, as defined by the application
+
+  * means this property may be repeated.
+
+  The Contact properties should be Keys of Contact entities. They are
+  represented in the XML encoding as linked <gd:who> elements.
+  """
+  KIND_PROPERTIES = ['title', 'content', 'link', 'category', 'geoPt', 'rating']
+  CONTACT_PROPERTIES = ['from', 'to', 'cc', 'bcc', 'reply-to']
+
+  def __init__(self, title, kind='Message'):
+    GdKind.__init__(self, kind, title, Message.KIND_PROPERTIES,
+                    Message.CONTACT_PROPERTIES)
+
+
+class Event(GdKind):
+  """A calendar event.
+
+  Includes the event title, description, location, organizer, start and end
+  time, and other details.
+
+  This is the gd Event kind. See:
+  http://code.google.com/apis/gdata/common-elements.html#gdEventKind
+
+  These properties are meaningful. They are all optional.
+
+  property name  property type    meaning
+  -------------------------------------
+  title          string         event name
+  content        string         event description
+  author         string         the organizer's name
+  where          string*        human-readable location (not a GeoPt)
+  startTime      timestamp      start time
+  endTime        timestamp      end time
+  eventStatus    string         one of the Event.Status values
+  link           Link*          page with more information
+  category       Category*      tag or label associated with this event
+  attendee       Contact*       attendees and other related people
+
+  * means this property may be repeated.
+
+  The Contact properties should be Keys of Contact entities. They are
+  represented in the XML encoding as linked <gd:who> elements.
+  """
+  KIND_PROPERTIES = ['title', 'content', 'author', 'where', 'startTime',
+                     'endTime', 'eventStatus', 'link', 'category']
+  CONTACT_PROPERTIES = ['attendee']
+
+  class Status:
+    CONFIRMED = 'confirmed'
+    TENTATIVE = 'tentative'
+    CANCELED = 'canceled'
+
+  def __init__(self, title, kind='Event'):
+    GdKind.__init__(self, kind, title, Event.KIND_PROPERTIES,
+                    Event.CONTACT_PROPERTIES)
+
+  def ToXml(self):
+    """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and
+    gd:eventStatus.
+    """
+    xml = GdKind.HEADER % self.kind().lower()
+
+    self._kind_properties = set(Contact.KIND_PROPERTIES)
+    xml += self._KindPropertiesToXml()
+
+    if 'author' in self:
+      xml += """
+  <author><name>%s</name></author>""" % self['author']
+
+    if 'eventStatus' in self:
+      xml += """
+  <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
+    self['eventStatus'])
+
+    if 'where' in self:
+      lines = ['<gd:where valueString="%s" />' % val
+               for val in self._XmlEscapeValues('where')]
+      xml += '\n  ' + '\n  '.join(lines)
+
+    iso_format = '%Y-%m-%dT%H:%M:%S'
+    xml += '\n  <gd:when'
+    for key in ['startTime', 'endTime']:
+      if key in self:
+        xml += ' %s="%s"' % (key, self[key].isoformat())
+    xml += ' />'
+
+    self._kind_properties.update(['author', 'where', 'startTime', 'endTime',
+                                  'eventStatus'])
+    xml += self._ContactPropertiesToXml()
+    xml += self._LeftoverPropertiesToXml()
+    xml += GdKind.FOOTER
+    return xml
+
+
+class Contact(GdKind):
+  """A contact: a person, a venue such as a club or a restaurant, or an
+  organization.
+
+  This is the gd Contact kind. See:
+  http://code.google.com/apis/gdata/common-elements.html#gdContactKind
+
+  Most of the information about the contact is in the <gd:contactSection>
+  element; see the reference section for that element for details.
+
+  These properties are meaningful. They are all optional.
+
+  property name  property type    meaning
+  -------------------------------------
+  title          string         contact's name
+  content        string         notes
+  email          Email*         email address
+  geoPt          GeoPt*         geographic location
+  im             IM*            IM address
+  phoneNumber    Phonenumber*   phone number
+  postalAddress  PostalAddress* mailing address
+  link           Link*          link to more information
+  category       Category*      tag or label associated with this contact
+
+  * means this property may be repeated.
+  """
+  CONTACT_SECTION_HEADER = """
+  <gd:contactSection>"""
+  CONTACT_SECTION_FOOTER = """
+  </gd:contactSection>"""
+
+  KIND_PROPERTIES = ['title', 'content', 'link', 'category']
+
+  CONTACT_SECTION_PROPERTIES = ['email', 'geoPt', 'im', 'phoneNumber',
+                                'postalAddress']
+
+  def __init__(self, title, kind='Contact'):
+    GdKind.__init__(self, kind, title, Contact.KIND_PROPERTIES)
+
+  def ToXml(self):
+    """ Override GdKind.ToXml() to put some properties inside a
+    gd:contactSection.
+    """
+    xml = GdKind.HEADER % self.kind().lower()
+
+    self._kind_properties = set(Contact.KIND_PROPERTIES)
+    xml += self._KindPropertiesToXml()
+
+    xml += Contact.CONTACT_SECTION_HEADER
+    self._kind_properties = set(Contact.CONTACT_SECTION_PROPERTIES)
+    xml += self._KindPropertiesToXml()
+    xml += Contact.CONTACT_SECTION_FOOTER
+
+    self._kind_properties.update(Contact.KIND_PROPERTIES)
+    xml += self._LeftoverPropertiesToXml()
+    xml += GdKind.FOOTER
+    return xml