diff -r 261778de26ff -r 620f9b141567 thirdparty/google_appengine/google/appengine/api/datastore_entities.py --- /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 tags. + """ + + HEADER = u""" + """ + FOOTER = u""" +""" + + _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 + 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 + 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' % (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 += """ + + """ % (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 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 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 += """ + %s""" % self['author'] + + if 'eventStatus' in self: + xml += """ + """ % ( + self['eventStatus']) + + if 'where' in self: + lines = ['' % val + for val in self._XmlEscapeValues('where')] + xml += '\n ' + '\n '.join(lines) + + iso_format = '%Y-%m-%dT%H:%M:%S' + xml += '\n + 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 = """ + """ + CONTACT_SECTION_FOOTER = """ + """ + + 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