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).
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
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.
53 """Object mapper starts off with empty value."""
57 def set_value(self
, value
):
58 """Set value of instance to map to.
61 value: Instance that this mapper maps to.
67 raise yaml_errors
.DuplicateAttribute("Duplicate attribute '%s'." % 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.
79 """Object sequencer starts off with empty value."""
81 self
.constructor
= None
83 def set_constructor(self
, constructor
):
84 """Set object used for constructing new sequence instances.
87 constructor: Callable which can accept no arguments. Must return
88 an instance of the appropriate class for the container.
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.
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.
123 attribute: Repeated validator attribute to find type for.
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
134 def BuildDocument(self
):
135 """Instantiate new root validated object.
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.
146 top_value: Parent of nested object.
149 New instance of object mapper.
151 result
= _ObjectMapper()
154 if isinstance(top_value
, self
.default_class
):
155 result
.value
= top_value
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.
164 top_value: Parent of closing mapping object.
165 mapping: _ObjectMapper instance that is leaving scope.
168 mapping
.value
.CheckInitialized()
169 except validation
.ValidationError
:
180 error_str
= '<unknown>'
183 raise validation
.ValidationError(error_str
, e
)
185 def BuildSequence(self
, top_value
):
186 """New instance of object sequence.
189 top_value: Object that contains the new sequence.
192 A new _ObjectSequencer instance.
194 return _ObjectSequencer()
196 def MapTo(self
, subject
, key
, value
):
197 """Map key-value pair to an objects attribute.
200 subject: _ObjectMapper of object that will receive new attribute.
201 key: Key of attribute.
202 value: Value of new attribute.
205 UnexpectedAttribute when the key is not a validated attribute of
206 the subject value class.
208 assert isinstance(subject
.value
, validation
.ValidatedBase
)
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())
220 elif isinstance(value
, _ObjectSequencer
):
222 value
.set_constructor(self
._GetRepeated
(attribute
))
227 subject
.value
.Set(key
, value
)
228 except validation
.ValidationError
, e
:
236 error_str
= '<unknown>'
239 value_str
= str(value
)
241 value_str
= '<unknown>'
244 e
.message
= ("Unable to assign value '%s' to attribute '%s':\n%s" %
245 (value_str
, key
, error_str
))
251 error_str
= '<unknown>'
254 value_str
= str(value
)
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.
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
)
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.
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.
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.
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]