App Engine Python SDK version 1.9.2
[gae.git] / python / google / appengine / api / datastore_entities.py
blob6e2fcb137605f47bab5625909aceeaaeb9faa381
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
22 """Classes for common kinds, including Contact, Message, and Event.
24 Most of these kinds are based on the gd namespace "kinds" from GData:
26 http://code.google.com/apis/gdata/common-elements.html
27 """
38 import types
39 import urlparse
40 from xml.sax import saxutils
41 from google.appengine.datastore import datastore_pb
42 from google.appengine.api import datastore
43 from google.appengine.api import datastore_errors
44 from google.appengine.api import datastore_types
46 class GdKind(datastore.Entity):
47 """ A base class for gd namespace kinds.
49 This class contains common logic for all gd namespace kinds. For example,
50 this class translates datastore (app id, kind, key) tuples to tag:
51 URIs appropriate for use in <key> tags.
52 """
54 HEADER = u"""<entry xmlns:gd='http://schemas.google.com/g/2005'>
55 <category scheme='http://schemas.google.com/g/2005#kind'
56 term='http://schemas.google.com/g/2005#%s' />"""
57 FOOTER = u"""
58 </entry>"""
60 _kind_properties = set()
61 _contact_properties = set()
63 def __init__(self, kind, title, kind_properties, contact_properties=[]):
64 """ Ctor.
66 title is the name of this particular entity, e.g. Bob Jones or Mom's
67 Birthday Party.
69 kind_properties is a list of property names that should be included in
70 this entity's XML encoding as first-class XML elements, instead of
71 <property> elements. 'title' and 'content' are added to kind_properties
72 automatically, and may not appear in contact_properties.
74 contact_properties is a list of property names that are Keys that point to
75 Contact entities, and should be included in this entity's XML encoding as
76 <gd:who> elements. If a property name is included in both kind_properties
77 and contact_properties, it is treated as a Contact property.
79 Args:
80 kind: string
81 title: string
82 kind_properties: list of strings
83 contact_properties: list of string
84 """
85 datastore.Entity.__init__(self, kind)
87 if not isinstance(title, types.StringTypes):
88 raise datastore_errors.BadValueError(
89 'Expected a string for title; received %s (a %s).' %
90 (title, datastore_types.typename(title)))
91 self['title'] = title
92 self['content'] = ''
95 self._contact_properties = set(contact_properties)
96 assert not self._contact_properties.intersection(self.keys())
98 self._kind_properties = set(kind_properties) - self._contact_properties
99 self._kind_properties.add('title')
100 self._kind_properties.add('content')
102 def _KindPropertiesToXml(self):
103 """ Convert the properties that are part of this gd kind to XML. For
104 testability, the XML elements in the output are sorted alphabetically
105 by property name.
107 Returns:
108 string # the XML representation of the gd kind properties
110 properties = self._kind_properties.intersection(set(self.keys()))
112 xml = u''
113 for prop in sorted(properties):
114 prop_xml = saxutils.quoteattr(prop)[1:-1]
116 value = self[prop]
117 has_toxml = (hasattr(value, 'ToXml') or
118 isinstance(value, list) and hasattr(value[0], 'ToXml'))
120 for val in self._XmlEscapeValues(prop):
123 if has_toxml:
124 xml += '\n %s' % val
125 else:
126 xml += '\n <%s>%s</%s>' % (prop_xml, val, prop_xml)
128 return xml
131 def _ContactPropertiesToXml(self):
132 """ Convert this kind's Contact properties kind to XML. For testability,
133 the XML elements in the output are sorted alphabetically by property name.
135 Returns:
136 string # the XML representation of the Contact properties
138 properties = self._contact_properties.intersection(set(self.keys()))
140 xml = u''
141 for prop in sorted(properties):
142 values = self[prop]
143 if not isinstance(values, list):
144 values = [values]
146 for value in values:
147 assert isinstance(value, datastore_types.Key)
148 xml += """
149 <gd:who rel="http://schemas.google.com/g/2005#%s.%s>
150 <gd:entryLink href="%s" />
151 </gd:who>""" % (self.kind().lower(), prop, value.ToTagUri())
153 return xml
156 def _LeftoverPropertiesToXml(self):
157 """ Convert all of this entity's properties that *aren't* part of this gd
158 kind to XML.
160 Returns:
161 string # the XML representation of the leftover properties
163 leftovers = set(self.keys())
164 leftovers -= self._kind_properties
165 leftovers -= self._contact_properties
166 if leftovers:
167 return u'\n ' + '\n '.join(self._PropertiesToXml(leftovers))
168 else:
169 return u''
171 def ToXml(self):
172 """ Returns an XML representation of this entity, as a string.
174 xml = GdKind.HEADER % self.kind().lower()
175 xml += self._KindPropertiesToXml()
176 xml += self._ContactPropertiesToXml()
177 xml += self._LeftoverPropertiesToXml()
178 xml += GdKind.FOOTER
179 return xml
182 class Message(GdKind):
183 """A message, such as an email, a discussion group posting, or a comment.
185 Includes the message title, contents, participants, and other properties.
187 This is the gd Message kind. See:
188 http://code.google.com/apis/gdata/common-elements.html#gdMessageKind
190 These properties are meaningful. They are all optional.
192 property name property type meaning
193 -------------------------------------
194 title string message subject
195 content string message body
196 from Contact* sender
197 to Contact* primary recipient
198 cc Contact* CC recipient
199 bcc Contact* BCC recipient
200 reply-to Contact* intended recipient of replies
201 link Link* attachment
202 category Category* tag or label associated with this message
203 geoPt GeoPt* geographic location the message was posted from
204 rating Rating* message rating, as defined by the application
206 * means this property may be repeated.
208 The Contact properties should be Keys of Contact entities. They are
209 represented in the XML encoding as linked <gd:who> elements.
211 KIND_PROPERTIES = ['title', 'content', 'link', 'category', 'geoPt', 'rating']
212 CONTACT_PROPERTIES = ['from', 'to', 'cc', 'bcc', 'reply-to']
214 def __init__(self, title, kind='Message'):
215 GdKind.__init__(self, kind, title, Message.KIND_PROPERTIES,
216 Message.CONTACT_PROPERTIES)
219 class Event(GdKind):
220 """A calendar event.
222 Includes the event title, description, location, organizer, start and end
223 time, and other details.
225 This is the gd Event kind. See:
226 http://code.google.com/apis/gdata/common-elements.html#gdEventKind
228 These properties are meaningful. They are all optional.
230 property name property type meaning
231 -------------------------------------
232 title string event name
233 content string event description
234 author string the organizer's name
235 where string* human-readable location (not a GeoPt)
236 startTime timestamp start time
237 endTime timestamp end time
238 eventStatus string one of the Event.Status values
239 link Link* page with more information
240 category Category* tag or label associated with this event
241 attendee Contact* attendees and other related people
243 * means this property may be repeated.
245 The Contact properties should be Keys of Contact entities. They are
246 represented in the XML encoding as linked <gd:who> elements.
248 KIND_PROPERTIES = ['title', 'content', 'author', 'where', 'startTime',
249 'endTime', 'eventStatus', 'link', 'category']
250 CONTACT_PROPERTIES = ['attendee']
252 class Status:
253 CONFIRMED = 'confirmed'
254 TENTATIVE = 'tentative'
255 CANCELED = 'canceled'
257 def __init__(self, title, kind='Event'):
258 GdKind.__init__(self, kind, title, Event.KIND_PROPERTIES,
259 Event.CONTACT_PROPERTIES)
261 def ToXml(self):
262 """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and
263 gd:eventStatus.
265 xml = GdKind.HEADER % self.kind().lower()
267 self._kind_properties = set(Contact.KIND_PROPERTIES)
268 xml += self._KindPropertiesToXml()
271 if 'author' in self:
272 xml += """
273 <author><name>%s</name></author>""" % self['author']
276 if 'eventStatus' in self:
277 xml += """
278 <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
279 self['eventStatus'])
282 if 'where' in self:
283 lines = ['<gd:where valueString="%s" />' % val
284 for val in self._XmlEscapeValues('where')]
285 xml += '\n ' + '\n '.join(lines)
288 iso_format = '%Y-%m-%dT%H:%M:%S'
289 xml += '\n <gd:when'
290 for key in ['startTime', 'endTime']:
291 if key in self:
292 xml += ' %s="%s"' % (key, self[key].isoformat())
293 xml += ' />'
295 self._kind_properties.update(['author', 'where', 'startTime', 'endTime',
296 'eventStatus'])
297 xml += self._ContactPropertiesToXml()
298 xml += self._LeftoverPropertiesToXml()
299 xml += GdKind.FOOTER
300 return xml
303 class Contact(GdKind):
304 """A contact: a person, a venue such as a club or a restaurant, or an
305 organization.
307 This is the gd Contact kind. See:
308 http://code.google.com/apis/gdata/common-elements.html#gdContactKind
310 Most of the information about the contact is in the <gd:contactSection>
311 element; see the reference section for that element for details.
313 These properties are meaningful. They are all optional.
315 property name property type meaning
316 -------------------------------------
317 title string contact's name
318 content string notes
319 email Email* email address
320 geoPt GeoPt* geographic location
321 im IM* IM address
322 phoneNumber Phonenumber* phone number
323 postalAddress PostalAddress* mailing address
324 link Link* link to more information
325 category Category* tag or label associated with this contact
327 * means this property may be repeated.
329 CONTACT_SECTION_HEADER = """
330 <gd:contactSection>"""
331 CONTACT_SECTION_FOOTER = """
332 </gd:contactSection>"""
335 KIND_PROPERTIES = ['title', 'content', 'link', 'category']
338 CONTACT_SECTION_PROPERTIES = ['email', 'geoPt', 'im', 'phoneNumber',
339 'postalAddress']
341 def __init__(self, title, kind='Contact'):
342 GdKind.__init__(self, kind, title, Contact.KIND_PROPERTIES)
344 def ToXml(self):
345 """ Override GdKind.ToXml() to put some properties inside a
346 gd:contactSection.
348 xml = GdKind.HEADER % self.kind().lower()
351 self._kind_properties = set(Contact.KIND_PROPERTIES)
352 xml += self._KindPropertiesToXml()
355 xml += Contact.CONTACT_SECTION_HEADER
356 self._kind_properties = set(Contact.CONTACT_SECTION_PROPERTIES)
357 xml += self._KindPropertiesToXml()
358 xml += Contact.CONTACT_SECTION_FOOTER
360 self._kind_properties.update(Contact.KIND_PROPERTIES)
361 xml += self._LeftoverPropertiesToXml()
362 xml += GdKind.FOOTER
363 return xml