App Engine Python SDK version 1.8.9
[gae.git] / python / google / appengine / tools / indexes_xml_parser.py
blobbdfdf63b141a7bd50be254fbd5c43c4b71b1fd9f
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.tools import xml_parser_utils
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 GetIndexYaml(unused_application, indexes_xml_str):
39 return _MakeIndexesListIntoYaml(
40 IndexesXmlParser().ProcessXml(indexes_xml_str))
43 def _MakeIndexesListIntoYaml(indexes_list):
44 """Converts list of yaml statements about datastore indexes into a string."""
45 statements = ['indexes:']
46 for index in indexes_list:
47 statements += index.ToYaml()
48 return '\n'.join(statements) + '\n'
51 class IndexesXmlParser(object):
52 """Provides logic for walking down XML tree and pulling data."""
54 def ProcessXml(self, xml_str):
55 """Parses XML string and returns object representation of relevant info.
57 Args:
58 xml_str: The XML string.
59 Returns:
60 A list of Index objects containing information about datastore indexes
61 from the XML.
62 Raises:
63 AppEngineConfigException: In case of malformed XML or illegal inputs.
64 """
66 try:
67 self.indexes = []
68 self.errors = []
69 xml_root = ElementTree.fromstring(xml_str)
70 if xml_parser_utils.GetTag(xml_root) != 'datastore-indexes':
71 raise AppEngineConfigException('Root tag must be <datastore-indexes>')
73 for child in xml_root.getchildren():
74 self.ProcessIndexNode(child)
76 if self.errors:
77 raise AppEngineConfigException('\n'.join(self.errors))
79 return self.indexes
80 except ElementTree.ParseError:
81 raise AppEngineConfigException('Bad input -- not valid XML')
83 def ProcessIndexNode(self, node):
84 """Processes XML <datastore-index> nodes into Index objects.
86 The following information is parsed out:
87 kind: specifies the kind of entities to index.
88 ancestor: true if the index supports queries that filter by
89 ancestor-key to constraint results to a single entity group.
90 property: represents the entity properties to index, with a name
91 and direction attribute.
93 Args:
94 node: <datastore-index> XML node in datastore-indexes.xml.
95 """
96 tag = xml_parser_utils.GetTag(node)
97 if tag != 'datastore-index':
98 self.errors.append('Unrecognized node: <%s>' % tag)
99 return
101 index = Index()
102 index.kind = xml_parser_utils.GetAttribute(node, 'kind')
103 if not index.kind:
104 self.errors.append(MISSING_KIND)
105 index.ancestor = xml_parser_utils.BooleanValue(
106 xml_parser_utils.GetAttribute(node, 'ancestor'))
107 index.properties = {}
108 for property_node in xml_parser_utils.GetNodes(node, 'property'):
109 name = xml_parser_utils.GetAttribute(property_node, 'name')
110 if not name:
111 self.errors.append(NAME_MISSING % index.kind)
112 continue
114 direction = (xml_parser_utils.GetAttribute(property_node, 'direction')
115 or 'asc')
116 if direction not in ('asc', 'desc'):
117 self.errors.append(BAD_DIRECTION % direction)
118 continue
119 index.properties[name] = direction
120 self.indexes.append(index)
123 class Index(object):
125 def ToYaml(self):
126 statements = ['- kind: "%s"' % self.kind]
127 if self.ancestor:
128 statements.append(' ancestor: yes')
129 if self.properties:
130 statements.append(' properties:')
131 for name in sorted(self.properties):
132 statements += [' - name: "%s"' % name,
133 ' direction: %s' % self.properties[name]]
134 return statements