1.9.30 sync.
[gae.git] / python / google / appengine / api / yaml_object.py
blob6c9ae84a6860511dd575620eae3cae7e2b3d36af
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.
21 """Builder for mapping YAML documents to object instances.
23 ObjectBuilder is responsible for mapping a YAML document to classes defined
24 using the validation mechanism (see google.appengine.api.validation.py).
25 """
36 from google.appengine.api import validation
37 from google.appengine.api import yaml_listener
38 from google.appengine.api import yaml_builder
39 from google.appengine.api import yaml_errors
41 import yaml
44 class _ObjectMapper(object):
45 """Wrapper used for mapping attributes from a yaml file to an object.
47 This wrapper is required because objects do not know what property they are
48 associated with a creation time, and therefore can not be instantiated
49 with the correct class until they are mapped to their parents.
50 """
52 def __init__(self):
53 """Object mapper starts off with empty value."""
54 self.value = None
55 self.seen = set()
57 def set_value(self, value):
58 """Set value of instance to map to.
60 Args:
61 value: Instance that this mapper maps to.
62 """
63 self.value = value
65 def see(self, key):
66 if key in self.seen:
67 raise yaml_errors.DuplicateAttribute("Duplicate attribute '%s'." % key)
68 self.seen.add(key)
70 class _ObjectSequencer(object):
71 """Wrapper used for building sequences from a yaml file to a list.
73 This wrapper is required because objects do not know what property they are
74 associated with a creation time, and therefore can not be instantiated
75 with the correct class until they are mapped to their parents.
76 """
78 def __init__(self):
79 """Object sequencer starts off with empty value."""
80 self.value = []
81 self.constructor = None
83 def set_constructor(self, constructor):
84 """Set object used for constructing new sequence instances.
86 Args:
87 constructor: Callable which can accept no arguments. Must return
88 an instance of the appropriate class for the container.
89 """
90 self.constructor = constructor
93 class ObjectBuilder(yaml_builder.Builder):
94 """Builder used for constructing validated objects.
96 Given a class that implements validation.ValidatedBase, it will parse a YAML
97 document and attempt to build an instance of the class.
98 ObjectBuilder will only map YAML fields that are accepted by the
99 ValidatedBase's GetValidator function.
100 Lists are mapped to validated. Repeated attributes and maps are mapped to
101 validated.Type properties.
103 For a YAML map to be compatible with a class, the class must have a
104 constructor that can be called with no parameters. If the provided type
105 does not have such a constructor a parse time error will occur.
108 def __init__(self, default_class):
109 """Initialize validated object builder.
111 Args:
112 default_class: Class that is instantiated upon the detection of a new
113 document. An instance of this class will act as the document itself.
115 self.default_class = default_class
117 def _GetRepeated(self, attribute):
118 """Get the ultimate type of a repeated validator.
120 Looks for an instance of validation.Repeated, returning its constructor.
122 Args:
123 attribute: Repeated validator attribute to find type for.
125 Returns:
126 The expected class of of the Type validator, otherwise object.
128 if isinstance(attribute, validation.Optional):
129 attribute = attribute.validator
130 if isinstance(attribute, validation.Repeated):
131 return attribute.constructor
132 return object
134 def BuildDocument(self):
135 """Instantiate new root validated object.
137 Returns:
138 New instance of validated object.
140 return self.default_class()
142 def BuildMapping(self, top_value):
143 """New instance of object mapper for opening map scope.
145 Args:
146 top_value: Parent of nested object.
148 Returns:
149 New instance of object mapper.
151 result = _ObjectMapper()
154 if isinstance(top_value, self.default_class):
155 result.value = top_value
156 return result
158 def EndMapping(self, top_value, mapping):
159 """When leaving scope, makes sure new object is initialized.
161 This method is mainly for picking up on any missing required attributes.
163 Args:
164 top_value: Parent of closing mapping object.
165 mapping: _ObjectMapper instance that is leaving scope.
167 try:
168 mapping.value.CheckInitialized()
169 except validation.ValidationError:
171 raise
172 except Exception, e:
177 try:
178 error_str = str(e)
179 except Exception:
180 error_str = '<unknown>'
183 raise validation.ValidationError(error_str, e)
185 def BuildSequence(self, top_value):
186 """New instance of object sequence.
188 Args:
189 top_value: Object that contains the new sequence.
191 Returns:
192 A new _ObjectSequencer instance.
194 return _ObjectSequencer()
196 def MapTo(self, subject, key, value):
197 """Map key-value pair to an objects attribute.
199 Args:
200 subject: _ObjectMapper of object that will receive new attribute.
201 key: Key of attribute.
202 value: Value of new attribute.
204 Raises:
205 UnexpectedAttribute when the key is not a validated attribute of
206 the subject value class.
208 assert isinstance(subject.value, validation.ValidatedBase)
210 try:
211 attribute = subject.value.GetValidator(key)
212 except validation.ValidationError, err:
213 raise yaml_errors.UnexpectedAttribute(err)
215 if isinstance(value, _ObjectMapper):
218 value.set_value(attribute.expected_type())
219 value = value.value
220 elif isinstance(value, _ObjectSequencer):
222 value.set_constructor(self._GetRepeated(attribute))
223 value = value.value
225 subject.see(key)
226 try:
227 subject.value.Set(key, value)
228 except validation.ValidationError, e:
233 try:
234 error_str = str(e)
235 except Exception:
236 error_str = '<unknown>'
238 try:
239 value_str = str(value)
240 except Exception:
241 value_str = '<unknown>'
244 e.message = ("Unable to assign value '%s' to attribute '%s':\n%s" %
245 (value_str, key, error_str))
246 raise e
247 except Exception, e:
248 try:
249 error_str = str(e)
250 except Exception:
251 error_str = '<unknown>'
253 try:
254 value_str = str(value)
255 except Exception:
256 value_str = '<unknown>'
259 message = ("Unable to assign value '%s' to attribute '%s':\n%s" %
260 (value_str, key, error_str))
261 raise validation.ValidationError(message, e)
263 def AppendTo(self, subject, value):
264 """Append a value to a sequence.
266 Args:
267 subject: _ObjectSequence that is receiving new value.
268 value: Value that is being appended to sequence.
270 if isinstance(value, _ObjectMapper):
272 value.set_value(subject.constructor())
273 subject.value.append(value.value)
274 else:
276 subject.value.append(value)
279 def BuildObjects(default_class, stream, loader=yaml.loader.SafeLoader):
280 """Build objects from stream.
282 Handles the basic case of loading all the objects from a stream.
284 Args:
285 default_class: Class that is instantiated upon the detection of a new
286 document. An instance of this class will act as the document itself.
287 stream: String document or open file object to process as per the
288 yaml.parse method. Any object that implements a 'read()' method which
289 returns a string document will work with the YAML parser.
290 loader_class: Used for dependency injection.
292 Returns:
293 List of default_class instances parsed from the stream.
295 builder = ObjectBuilder(default_class)
296 handler = yaml_builder.BuilderHandler(builder)
297 listener = yaml_listener.EventListener(handler)
299 listener.Parse(stream, loader)
300 return handler.GetResults()
303 def BuildSingleObject(default_class, stream, loader=yaml.loader.SafeLoader):
304 """Build object from stream.
306 Handles the basic case of loading a single object from a stream.
308 Args:
309 default_class: Class that is instantiated upon the detection of a new
310 document. An instance of this class will act as the document itself.
311 stream: String document or open file object to process as per the
312 yaml.parse method. Any object that implements a 'read()' method which
313 returns a string document will work with the YAML parser.
314 loader_class: Used for dependency injection.
316 definitions = BuildObjects(default_class, stream, loader)
318 if len(definitions) < 1:
319 raise yaml_errors.EmptyConfigurationFile()
320 if len(definitions) > 1:
321 raise yaml_errors.MultipleConfigurationFile()
322 return definitions[0]