|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
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 |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
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. |
|
16 # |
|
17 |
|
18 """Builder for mapping YAML documents to object instances. |
|
19 |
|
20 ObjectBuilder is responsible for mapping a YAML document to classes defined |
|
21 using the validation mechanism (see google.appengine.api.validation.py). |
|
22 """ |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 from google.appengine.api import validation |
|
29 from google.appengine.api import yaml_listener |
|
30 from google.appengine.api import yaml_builder |
|
31 from google.appengine.api import yaml_errors |
|
32 |
|
33 import yaml |
|
34 |
|
35 |
|
36 class _ObjectMapper(object): |
|
37 """Wrapper used for mapping attributes from a yaml file to an object. |
|
38 |
|
39 This wrapper is required because objects do not know what property they are |
|
40 associated with a creation time, and therefore can not be instantiated |
|
41 with the correct class until they are mapped to their parents. |
|
42 """ |
|
43 |
|
44 def __init__(self): |
|
45 """Object mapper starts off with empty value.""" |
|
46 self.value = None |
|
47 self.seen = set() |
|
48 |
|
49 def set_value(self, value): |
|
50 """Set value of instance to map to. |
|
51 |
|
52 Args: |
|
53 value: Instance that this mapper maps to. |
|
54 """ |
|
55 self.value = value |
|
56 |
|
57 def see(self, key): |
|
58 if key in self.seen: |
|
59 raise yaml_errors.DuplicateAttribute("Duplicate attribute '%s'." % key) |
|
60 self.seen.add(key) |
|
61 |
|
62 class _ObjectSequencer(object): |
|
63 """Wrapper used for building sequences from a yaml file to a list. |
|
64 |
|
65 This wrapper is required because objects do not know what property they are |
|
66 associated with a creation time, and therefore can not be instantiated |
|
67 with the correct class until they are mapped to their parents. |
|
68 """ |
|
69 |
|
70 def __init__(self): |
|
71 """Object sequencer starts off with empty value.""" |
|
72 self.value = [] |
|
73 self.constructor = None |
|
74 |
|
75 def set_constructor(self, constructor): |
|
76 """Set object used for constructing new sequence instances. |
|
77 |
|
78 Args: |
|
79 constructor: Callable which can accept no arguments. Must return |
|
80 an instance of the appropriate class for the container. |
|
81 """ |
|
82 self.constructor = constructor |
|
83 |
|
84 |
|
85 class ObjectBuilder(yaml_builder.Builder): |
|
86 """Builder used for constructing validated objects. |
|
87 |
|
88 Given a class that implements validation.Validated, it will parse a YAML |
|
89 document and attempt to build an instance of the class. It does so by mapping |
|
90 YAML keys to Python attributes. ObjectBuilder will only map YAML fields |
|
91 to attributes defined in the Validated subclasses 'ATTRIBUTE' definitions. |
|
92 Lists are mapped to validated. Repeated attributes and maps are mapped to |
|
93 validated.Type properties. |
|
94 |
|
95 For a YAML map to be compatible with a class, the class must have a |
|
96 constructor that can be called with no parameters. If the provided type |
|
97 does not have such a constructor a parse time error will occur. |
|
98 """ |
|
99 |
|
100 def __init__(self, default_class): |
|
101 """Initialize validated object builder. |
|
102 |
|
103 Args: |
|
104 default_class: Class that is instantiated upon the detection of a new |
|
105 document. An instance of this class will act as the document itself. |
|
106 """ |
|
107 self.default_class = default_class |
|
108 |
|
109 def _GetRepeated(self, attribute): |
|
110 """Get the ultimate type of a repeated validator. |
|
111 |
|
112 Looks for an instance of validation.Repeated, returning its constructor. |
|
113 |
|
114 Args: |
|
115 attribute: Repeated validator attribute to find type for. |
|
116 |
|
117 Returns: |
|
118 The expected class of of the Type validator, otherwise object. |
|
119 """ |
|
120 if isinstance(attribute, validation.Optional): |
|
121 attribute = attribute.validator |
|
122 if isinstance(attribute, validation.Repeated): |
|
123 return attribute.constructor |
|
124 return object |
|
125 |
|
126 def BuildDocument(self): |
|
127 """Instantiate new root validated object. |
|
128 |
|
129 Returns: |
|
130 New instance of validated object. |
|
131 """ |
|
132 return self.default_class() |
|
133 |
|
134 def BuildMapping(self, top_value): |
|
135 """New instance of object mapper for opening map scope. |
|
136 |
|
137 Args: |
|
138 top_value: Parent of nested object. |
|
139 |
|
140 Returns: |
|
141 New instance of object mapper. |
|
142 """ |
|
143 result = _ObjectMapper() |
|
144 if isinstance(top_value, self.default_class): |
|
145 result.value = top_value |
|
146 return result |
|
147 |
|
148 def EndMapping(self, top_value, mapping): |
|
149 """When leaving scope, makes sure new object is initialized. |
|
150 |
|
151 This method is mainly for picking up on any missing required attributes. |
|
152 |
|
153 Args: |
|
154 top_value: Parent of closing mapping object. |
|
155 mapping: _ObjectMapper instance that is leaving scope. |
|
156 """ |
|
157 try: |
|
158 mapping.value.CheckInitialized() |
|
159 except validation.ValidationError: |
|
160 raise |
|
161 except Exception, e: |
|
162 try: |
|
163 error_str = str(e) |
|
164 except Exception: |
|
165 error_str = '<unknown>' |
|
166 |
|
167 raise validation.ValidationError("Invalid object:\n%s" % error_str, e) |
|
168 |
|
169 def BuildSequence(self, top_value): |
|
170 """New instance of object sequence. |
|
171 |
|
172 Args: |
|
173 top_value: Object that contains the new sequence. |
|
174 |
|
175 Returns: |
|
176 A new _ObjectSequencer instance. |
|
177 """ |
|
178 return _ObjectSequencer() |
|
179 |
|
180 def MapTo(self, subject, key, value): |
|
181 """Map key-value pair to an objects attribute. |
|
182 |
|
183 Args: |
|
184 subject: _ObjectMapper of object that will receive new attribute. |
|
185 key: Key of attribute. |
|
186 value: Value of new attribute. |
|
187 |
|
188 Raises: |
|
189 UnexpectedAttribute when the key is not a validated attribute of |
|
190 the subject value class. |
|
191 """ |
|
192 assert subject.value is not None |
|
193 if key not in subject.value.ATTRIBUTES: |
|
194 raise yaml_errors.UnexpectedAttribute( |
|
195 'Unexpected attribute \'%s\' for object of type %s.' % |
|
196 (key, str(subject.value.__class__))) |
|
197 |
|
198 if isinstance(value, _ObjectMapper): |
|
199 value.set_value(subject.value.GetAttribute(key).expected_type()) |
|
200 value = value.value |
|
201 elif isinstance(value, _ObjectSequencer): |
|
202 value.set_constructor(self._GetRepeated(subject.value.ATTRIBUTES[key])) |
|
203 value = value.value |
|
204 |
|
205 subject.see(key) |
|
206 try: |
|
207 setattr(subject.value, key, value) |
|
208 except validation.ValidationError, e: |
|
209 try: |
|
210 error_str = str(e) |
|
211 except Exception: |
|
212 error_str = '<unknown>' |
|
213 |
|
214 try: |
|
215 value_str = str(value) |
|
216 except Exception: |
|
217 value_str = '<unknown>' |
|
218 |
|
219 e.message = ("Unable to assign value '%s' to attribute '%s':\n%s" % |
|
220 (value_str, key, error_str)) |
|
221 raise e |
|
222 except Exception, e: |
|
223 try: |
|
224 error_str = str(e) |
|
225 except Exception: |
|
226 error_str = '<unknown>' |
|
227 |
|
228 try: |
|
229 value_str = str(value) |
|
230 except Exception: |
|
231 value_str = '<unknown>' |
|
232 |
|
233 message = ("Unable to assign value '%s' to attribute '%s':\n%s" % |
|
234 (value_str, key, error_str)) |
|
235 raise validation.ValidationError(message, e) |
|
236 |
|
237 def AppendTo(self, subject, value): |
|
238 """Append a value to a sequence. |
|
239 |
|
240 Args: |
|
241 subject: _ObjectSequence that is receiving new value. |
|
242 value: Value that is being appended to sequence. |
|
243 """ |
|
244 if isinstance(value, _ObjectMapper): |
|
245 value.set_value(subject.constructor()) |
|
246 subject.value.append(value.value) |
|
247 else: |
|
248 subject.value.append(value) |
|
249 |
|
250 |
|
251 def BuildObjects(default_class, stream, loader=yaml.loader.SafeLoader): |
|
252 """Build objects from stream. |
|
253 |
|
254 Handles the basic case of loading all the objects from a stream. |
|
255 |
|
256 Args: |
|
257 default_class: Class that is instantiated upon the detection of a new |
|
258 document. An instance of this class will act as the document itself. |
|
259 stream: String document or open file object to process as per the |
|
260 yaml.parse method. Any object that implements a 'read()' method which |
|
261 returns a string document will work with the YAML parser. |
|
262 loader_class: Used for dependency injection. |
|
263 |
|
264 Returns: |
|
265 List of default_class instances parsed from the stream. |
|
266 """ |
|
267 builder = ObjectBuilder(default_class) |
|
268 handler = yaml_builder.BuilderHandler(builder) |
|
269 listener = yaml_listener.EventListener(handler) |
|
270 |
|
271 listener.Parse(stream, loader) |
|
272 return handler.GetResults() |
|
273 |
|
274 |
|
275 def BuildSingleObject(default_class, stream, loader=yaml.loader.SafeLoader): |
|
276 """Build object from stream. |
|
277 |
|
278 Handles the basic case of loading a single object from a stream. |
|
279 |
|
280 Args: |
|
281 default_class: Class that is instantiated upon the detection of a new |
|
282 document. An instance of this class will act as the document itself. |
|
283 stream: String document or open file object to process as per the |
|
284 yaml.parse method. Any object that implements a 'read()' method which |
|
285 returns a string document will work with the YAML parser. |
|
286 loader_class: Used for dependency injection. |
|
287 """ |
|
288 definitions = BuildObjects(default_class, stream, loader) |
|
289 |
|
290 if len(definitions) < 1: |
|
291 raise yaml_errors.EmptyConfigurationFile() |
|
292 if len(definitions) > 1: |
|
293 raise yaml_errors.MultipleConfigurationFile() |
|
294 return definitions[0] |