359 lines
12 KiB
Python
359 lines
12 KiB
Python
# Copyright 2006 Google LLC. All Rights Reserved.
|
|
#
|
|
# 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:
|
|
|
|
https://developers.google.com/gdata/docs/1.0/elements
|
|
"""
|
|
|
|
|
|
# WARNING: This file is externally viewable by our users. All comments from
|
|
# this file will be stripped. The docstrings will NOT. Do not put sensitive
|
|
# information in docstrings. If you must communicate internal information in
|
|
# this source file, please place them in comments only.
|
|
|
|
# TODO(user): rename this file datastore_kinds.py
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import unicode_literals
|
|
|
|
from xml.sax import saxutils
|
|
|
|
from googlecloudsdk.appengine._internal import six_subset
|
|
from googlecloudsdk.appengine.api import datastore
|
|
from googlecloudsdk.appengine.api import datastore_errors
|
|
from googlecloudsdk.appengine.api import datastore_types
|
|
from googlecloudsdk.appengine.datastore import datastore_pb
|
|
|
|
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 = """<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 = """
|
|
</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, six_subset.string_types):
|
|
raise datastore_errors.BadValueError(
|
|
'Expected a string for title; received %s (a %s).' %
|
|
(title, datastore_types.typename(title)))
|
|
self['title'] = title
|
|
self['content'] = ''
|
|
|
|
# automatic properties (like title and content) can't be contact properties
|
|
self._contact_properties = set(contact_properties)
|
|
assert not self._contact_properties.intersection(list(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 = ''
|
|
for prop in sorted(properties):
|
|
prop_xml = saxutils.quoteattr(prop)[1:-1] # strip the enclosing quotes
|
|
|
|
value = self[prop]
|
|
has_toxml = (hasattr(value, 'ToXml') or
|
|
isinstance(value, list) and hasattr(value[0], 'ToXml'))
|
|
|
|
for val in self._XmlEscapeValues(prop):
|
|
# _XmlEscapeValues() puts XML tags around the property values
|
|
# *if they have a ToXml() method*. if not, it leaves them unchanged.
|
|
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 = ''
|
|
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 '\n ' + '\n '.join(self._PropertiesToXml(leftovers))
|
|
else:
|
|
return ''
|
|
|
|
def ToXml(self):
|
|
""" Returns an XML representation of this entity, as a string.
|
|
"""
|
|
xml = GdKind.HEADER % self.kind().lower() # lower case
|
|
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:
|
|
https://developers.google.com/gdata/docs/1.0/elements#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:
|
|
https://developers.google.com/gdata/docs/1.0/elements#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: # an enum
|
|
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() # lower case
|
|
|
|
self._kind_properties = set(Contact.KIND_PROPERTIES)
|
|
xml += self._KindPropertiesToXml()
|
|
|
|
# author becomes <author><name>
|
|
if 'author' in self:
|
|
xml += """
|
|
<author><name>%s</name></author>""" % self['author']
|
|
|
|
# eventStatus becomes gd:eventStatus
|
|
if 'eventStatus' in self:
|
|
xml += """
|
|
<gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
|
|
self['eventStatus'])
|
|
|
|
# where becomes gd:where
|
|
if 'where' in self:
|
|
lines = ['<gd:where valueString="%s" />' % val
|
|
for val in self._XmlEscapeValues('where')]
|
|
xml += '\n ' + '\n '.join(lines)
|
|
|
|
# startTime and endTime become gd:when
|
|
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:
|
|
https://developers.google.com/gdata/docs/1.0/elements#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>"""
|
|
|
|
# properties that go in top-level entry element
|
|
KIND_PROPERTIES = ['title', 'content', 'link', 'category']
|
|
|
|
# properties that go in gd:contactSection element
|
|
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() # lower case
|
|
|
|
# put the kind properties in the top level...
|
|
self._kind_properties = set(Contact.KIND_PROPERTIES)
|
|
xml += self._KindPropertiesToXml()
|
|
|
|
# ...and the contact section properties inside the gd:contactSection elem
|
|
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
|