App Engine Python SDK version 1.9.13
[gae.git] / python / google / appengine / datastore / datastore_index_xml.py
blob40d03dd4a4b491ede2977e5b8089ce397e48bef1
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.
17 """Directly processes text of datastore-indexes.xml.
19 IndexesXmlParser is called with an XML string to produce an IndexXml object
20 containing the data from the XML.
22 IndexesXmlParser: converts XML to Index object.
23 Index: describes a single index specified in datastore-indexes.xml
24 """
26 from xml.etree import ElementTree
28 from google.appengine.api.validation import ValidationError
29 from google.appengine.datastore.datastore_index import Index
30 from google.appengine.datastore.datastore_index import IndexDefinitions
31 from google.appengine.datastore.datastore_index import Property
33 MISSING_KIND = '<datastore-index> node has missing attribute "kind".'
34 BAD_DIRECTION = ('<property> tag attribute "direction" must have value "asc"'
35 ' or "desc", given "%s"')
36 NAME_MISSING = ('<datastore-index> node with kind "%s" needs to have a name'
37 ' attribute specified for its <property> node')
40 def IndexesXmlToIndexDefinitions(xml_str):
41 """Convert a <datastore-indexes> XML string into an IndexDefinitions objects.
43 Args:
44 xml_str: a string containing a complete XML document where the root node is
45 <datastore-indexes>.
47 Returns:
48 an IndexDefinitions object parsed out of the XML string.
50 Raises:
51 ValidationError: in case of malformed XML or illegal inputs.
52 """
53 parser = IndexesXmlParser()
54 return parser.Parse(xml_str)
57 def IsAutoGenerated(xml_str):
58 """Test if the given datastore-indexes.xml string implies auto-generation."""
59 try:
60 xml_root = ElementTree.fromstring(xml_str)
61 return (xml_root.tag == 'datastore-indexes' and
62 _BooleanAttribute(xml_root.attrib.get('autoGenerate', 'false')))
63 except ElementTree.ParseError:
64 return False
67 class IndexesXmlParser(object):
68 """Provides logic for walking down XML tree and pulling data."""
70 def Parse(self, xml_str):
71 """Parses XML string and returns object representation of relevant info.
73 Args:
74 xml_str: The XML string.
75 Returns:
76 An IndexDefinitions object containing the result of parsing the XML.
77 Raises:
78 ValidationError: In case of malformed XML or illegal inputs.
79 """
81 try:
82 self.indexes = []
83 self.errors = []
84 xml_root = ElementTree.fromstring(xml_str)
85 if xml_root.tag != 'datastore-indexes':
86 raise ValidationError('Root tag must be <datastore-indexes>')
88 for child in xml_root.getchildren():
89 self.ProcessIndexNode(child)
91 if self.errors:
92 raise ValidationError('\n'.join(self.errors))
94 return IndexDefinitions(indexes=self.indexes)
95 except ElementTree.ParseError, e:
96 raise ValidationError('Bad input -- not valid XML: %s' % e)
98 def ProcessIndexNode(self, node):
99 """Processes XML <datastore-index> nodes into Index objects.
101 The following information is parsed out:
102 kind: specifies the kind of entities to index.
103 ancestor: true if the index supports queries that filter by
104 ancestor-key to constraint results to a single entity group.
105 property: represents the entity properties to index, with a name
106 and direction attribute.
108 Args:
109 node: <datastore-index> XML node in datastore-indexes.xml.
111 if node.tag != 'datastore-index':
112 self.errors.append('Unrecognized node: <%s>' % node.tag)
113 return
115 index = Index()
116 index.kind = node.attrib.get('kind', '')
117 if not index.kind:
118 self.errors.append(MISSING_KIND)
119 ancestor = node.attrib.get('ancestor', 'false')
120 index.ancestor = _BooleanAttribute(ancestor)
121 if index.ancestor is None:
122 self.errors.append(
123 'Value for ancestor should be true or false, not "%s"' % ancestor)
124 properties = []
125 property_nodes = [n for n in node.getchildren() if n.tag == 'property']
126 for property_node in property_nodes:
127 name = property_node.attrib.get('name', '')
128 if not name:
129 self.errors.append(NAME_MISSING % index.kind)
130 continue
132 direction = property_node.attrib.get('direction', 'asc')
133 if direction not in ('asc', 'desc'):
134 self.errors.append(BAD_DIRECTION % direction)
135 continue
136 properties.append(Property(name=name, direction=direction))
137 index.properties = properties
138 self.indexes.append(index)
141 def _BooleanAttribute(value):
142 """Parse the given attribute value as a Boolean value.
144 This follows the specification here:
145 http://www.w3.org/TR/2012/REC-xmlschema11-2-20120405/datatypes.html#boolean
147 Args:
148 value: the value to parse.
150 Returns:
151 True if the value parses as true, False if it parses as false, None if it
152 parses as neither.
154 if value in ['true', '1']:
155 return True
156 elif value in ['false', '0']:
157 return False
158 else:
159 return None