App Engine Python SDK version 1.9.12
[gae.git] / python / google / appengine / tools / indexes_xml_parser.py
blob8ae3a31aee31860833fe7d53639e5a7b80c56aea
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 collections import OrderedDict
27 from xml.etree import ElementTree
29 from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
31 MISSING_KIND = '<datastore-index> node has missing attribute "kind".'
32 BAD_DIRECTION = ('<property> tag attribute "direction" must have value "asc"'
33 ' or "desc", given "%s"')
34 NAME_MISSING = ('<datastore-index> node with kind "%s" needs to have a name'
35 ' attribute specified for its <property> node')
38 def MakeIndexesListIntoYaml(indexes_list):
39 """Converts a list of parsed <datastore-index> clauses into YAML."""
40 statements = ['indexes:']
41 for index in indexes_list:
42 statements += index.ToYaml()
43 return '\n'.join(statements) + '\n'
46 class IndexesXmlParser(object):
47 """Provides logic for walking down XML tree and pulling data."""
49 def ProcessXml(self, xml_str):
50 """Parses XML string and returns object representation of relevant info.
52 Args:
53 xml_str: The XML string.
54 Returns:
55 A list of Index objects containing information about datastore indexes
56 from the XML.
57 Raises:
58 AppEngineConfigException: In case of malformed XML or illegal inputs.
59 """
61 try:
62 self.indexes = []
63 self.errors = []
64 xml_root = ElementTree.fromstring(xml_str)
65 if xml_root.tag != 'datastore-indexes':
66 raise AppEngineConfigException('Root tag must be <datastore-indexes>')
68 for child in xml_root.getchildren():
69 self.ProcessIndexNode(child)
71 if self.errors:
72 raise AppEngineConfigException('\n'.join(self.errors))
74 return self.indexes
75 except ElementTree.ParseError as e:
76 raise AppEngineConfigException('Bad input -- not valid XML: %s' % e)
78 def ProcessIndexNode(self, node):
79 """Processes XML <datastore-index> nodes into Index objects.
81 The following information is parsed out:
82 kind: specifies the kind of entities to index.
83 ancestor: true if the index supports queries that filter by
84 ancestor-key to constraint results to a single entity group.
85 property: represents the entity properties to index, with a name
86 and direction attribute.
88 Args:
89 node: <datastore-index> XML node in datastore-indexes.xml.
90 """
91 if node.tag != 'datastore-index':
92 self.errors.append('Unrecognized node: <%s>' % node.tag)
93 return
95 index = Index()
96 index.kind = node.attrib.get('kind', '')
97 if not index.kind:
98 self.errors.append(MISSING_KIND)
99 ancestor = node.attrib.get('ancestor', 'false')
100 index.ancestor = self._BooleanAttribute(ancestor)
101 if index.ancestor is None:
102 self.errors.append(
103 'Value for ancestor should be true or false, not "%s"' % ancestor)
104 index.properties = OrderedDict()
105 property_nodes = [n for n in node.getchildren() if n.tag == 'property']
106 for property_node in property_nodes:
107 name = property_node.attrib.get('name', '')
108 if not name:
109 self.errors.append(NAME_MISSING % index.kind)
110 continue
112 direction = property_node.attrib.get('direction', 'asc')
113 if direction not in ('asc', 'desc'):
114 self.errors.append(BAD_DIRECTION % direction)
115 continue
116 index.properties[name] = direction
117 self.indexes.append(index)
119 @staticmethod
120 def _BooleanAttribute(value):
121 """Parse the given attribute value as a Boolean value.
123 This follows the specification here:
124 http://www.w3.org/TR/2012/REC-xmlschema11-2-20120405/datatypes.html#boolean
126 Args:
127 value: the value to parse.
129 Returns:
130 True if the value parses as true, False if it parses as false, None if it
131 parses as neither.
133 if value in ['true', '1']:
134 return True
135 elif value in ['false', '0']:
136 return False
137 else:
138 return None
141 class Index(object):
143 def ToYaml(self):
144 statements = ['- kind: "%s"' % self.kind]
145 if self.ancestor:
146 statements.append(' ancestor: yes')
147 if self.properties:
148 statements.append(' properties:')
149 for name in self.properties:
150 statements += [' - name: "%s"' % name,
151 ' direction: %s' % self.properties[name]]
152 return statements