blob: e41efefde750ee77b787853a9c72cccc7f0ad579 [file] [log] [blame]
Ricky Liang1b72ccf2013-03-01 10:23:01 +08001#!/usr/bin/python -u
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8# pylint: disable=W0212, W0622
9
10"""A function to create a schema tree from the given schema expression.
11
12For example:
13
14 1. This is the schema of the encoded_fields in component database.
15
16 Dict('encoded_fields', Scalar('encoded_field', str),
17 Dict('encoded_indices', Scalar('encoded_index', int),
18 Dict('component_classes', Scalar('component_class', str),
19 AnyOf('component_names', [
20 Scalar('component_name', str),
21 List('list_of_component_names', Scalar('component_name', str)),
22 Scalar('none', type(None))
23 ])
24 )
25 )
26 )
27
28 2. This is the schema of the pattern in component database.
29
30 List('pattern',
31 Dict('pattern_field', key_type=Scalar('encoded_index', str),
32 value_type=Scalar('bit_offset', int))
33 )
34
35 3. This is the schema of the components in component database.
36
37 Dict('components', Scalar('component_class', str),
38 Dict('component_names', Scalar('component_name', str),
39 FixedDict('component_attributes',
40 items={
41 'value': AnyOf('probed_value', [
42 Scalar('probed_value', str),
43 List('list_of_probed_values', Scalar('probed_value', str))
44 ])
45 },
46 optional_items={
47 'labels': List('list_of_labels', Scalar('label', str))
48 }
49 )
50 )
51 )
52"""
53
54import copy
55import factory_common # pylint: disable=W0611
56
57
58class SchemaException(Exception):
59 pass
60
61
62class BaseType(object):
63 """Base type class for schema classes.
64 """
65 def __init__(self, label):
66 self._label = label
67
68 def Validate(self, data):
69 raise NotImplementedError
70
71
72class Scalar(BaseType):
73 """Scalar schema class.
74
75 Attributes:
76 label: A human-readable string to describe this Scalar.
77 element_type: The Python type of this Scalar. Cannot be a iterable type.
78
79 Raises:
80 SchemaException if argument format is incorrect.
81 """
82 def __init__(self, label, element_type):
83 super(Scalar, self).__init__(label)
84 if getattr(element_type, '__iter__', None):
85 raise SchemaException('Scalar element type %r is iterable')
86 self._element_type = element_type
87
88 def Validate(self, data):
89 """Validates the given data against the Scalar schema.
90
91 It checks if the data's type matches the Scalar's element type. Also, it
92 checks if the data's value matches the Scalar's value if the required value
93 is specified.
94
95 Args:
96 data: A Python data structure to be validated.
97
98 Raises:
99 SchemaException if validation fails.
100 """
101 if not isinstance(data, self._element_type):
102 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
103 (data, self._element_type, type(data)))
104
105
106class Dict(BaseType):
107 """Dict schema class.
108
109 This schema class is used to verify simple dict. Only the key type and value
110 type are validated.
111
112 Attributes:
113 label: A human-readable string to describe this Scalar.
114 key_type: A schema object indicating the schema of the keys of this Dict.
115 value_type: A schema object indicating the schema of the values of this
116 Dict.
117
118 Raises:
119 SchemaException if argument format is incorrect.
120 """
121 def __init__(self, label, key_type, value_type):
122 super(Dict, self).__init__(label)
123 if not isinstance(key_type, Scalar):
124 raise SchemaException('key_type %r of Dict %r is not Scalar' %
125 (key_type, self._label))
126 self._key_type = key_type
127 if not isinstance(value_type, BaseType):
128 raise SchemaException('value_type %r of Dict %r is not Schema object' %
129 (value_type, self._label))
130 self._value_type = value_type
131
132 def Validate(self, data):
133 """Validates the given data against the Dict schema.
134
135 It checks that all the keys in data matches the schema defined by key_type,
136 and all the values in data matches the schema defined by value_type.
137
138 Args:
139 data: A Python data structure to be validated.
140
141 Raises:
142 SchemaException if validation fails.
143 """
144 if not isinstance(data, dict):
145 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
146 (self._label, type(data)))
147 for k, v in data.iteritems():
148 self._key_type.Validate(k)
149 self._value_type.Validate(v)
150
151
152class FixedDict(BaseType):
153 """FixedDict schema class.
154
155 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
156 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
157 in XML.
158
159 An example FixedDict schema:
160 FixedDict('foo',
161 items={
162 'a': Scalar('bar', str),
163 'b': Scalar('buz', int)
164 }, optional_items={
165 'c': Scalar('boo', int)
166 })
167
168 Attributes:
169 label: A human-readable string to describe this dict.
170 items: A dict of required items that must be specified.
171 optional_items: A dict of optional items.
172
173 Raises:
174 SchemaException if argument format is incorrect.
175 """
176 def __init__(self, label, items=None, optional_items=None):
177 super(FixedDict, self).__init__(label)
178 if items and not isinstance(items, dict):
179 raise SchemaException('items of FixedDict %r should be a dict' %
180 self._label)
181 self._items = copy.deepcopy(items) if items is not None else {}
182 if optional_items and not isinstance(optional_items, dict):
183 raise SchemaException('optional_items of FixedDict %r should be a dict' %
184 self._label)
185 self._optional_items = (
186 copy.deepcopy(optional_items) if optional_items is not None else {})
187
188 def Validate(self, data):
189 """Validates the given data and all its key-value pairs against the Dict
190 schema.
191
192 If a key of Dict's type is required, then it must exist in the data's keys.
193
194 Args:
195 data: A Python data structure to be validated.
196
197 Raises:
198 SchemaException if validation fails.
199 """
200 if not isinstance(data, dict):
201 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
202 (self._label, type(data)))
203 data_key_list = data.keys()
204 # Check that every key-value pair in items exists in data
205 for key, value_schema in self._items.iteritems():
206 if key not in data:
207 raise SchemaException(
208 'Required item %r does not exist in FixedDict %r' %
209 (key, data))
210 value_schema.Validate(data[key])
211 data_key_list.remove(key)
212 # Check that all the remaining unmatched key-value pairs matches any
213 # definition in items or optional_items.
214 for key, value_schema in self._optional_items.iteritems():
215 if key not in data:
216 continue
217 value_schema.Validate(data[key])
218 data_key_list.remove(key)
219 if data_key_list:
220 raise SchemaException('Keys %r are undefined in FixedDict %r' %
221 (data_key_list, self._label))
222
223
224class List(BaseType):
225 """List schema class.
226
227 Attributes:
228 label: A string to describe this list.
229 element_type: Optional schema object to describe the type of the List.
230
231 Raises:
232 SchemaException if argument format is incorrect.
233 """
234 def __init__(self, label, element_type=None):
235 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800236 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800237 raise SchemaException(
238 'element_type %r of List %r is not a Schema object' %
239 (element_type, self._label))
Ricky Liang3e5342b2013-03-08 12:16:25 +0800240 self._element_type = copy.deepcopy(element_type)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800241
242 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800243 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800244
245 Args:
246 data: A Python data structure to be validated.
247
248 Raises:
249 SchemaException if validation fails.
250 """
251 if not isinstance(data, list):
252 raise SchemaException('Type mismatch on %r: expected list, got %r' %
253 (self._label, type(data)))
254 if self._element_type:
255 for data_value in data:
256 self._element_type.Validate(data_value)
257
258
Ricky Liang3e5342b2013-03-08 12:16:25 +0800259class Tuple(BaseType):
260 """Tuple schema class.
261
262 Comparing to List, the Tuple schema makes sure that every element exactly
263 matches the defined position and schema.
264
265 Attributes:
266 label: A string to describe this tuple.
267 element_types: Optional list or tuple schema object to describe the
268 types of the Tuple.
269
270 Raises:
271 SchemaException if argument format is incorrect.
272 """
273 def __init__(self, label, element_types=None):
274 super(Tuple, self).__init__(label)
275 if (element_types and
276 (not isinstance(element_types, (tuple, list))) or
277 (not all([isinstance(x, BaseType)] for x in element_types))):
278 raise SchemaException(
279 'element_types %r of Tuple %r is not a tuple or list' %
280 (element_types, self._label))
281 self._element_types = copy.deepcopy(element_types)
282
283 def Validate(self, data):
284 """Validates the given data and all its elements against the Tuple schema.
285
286 Args:
287 data: A Python data structure to be validated.
288
289 Raises:
290 SchemaException if validation fails.
291 """
292 if not isinstance(data, tuple):
293 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
294 (self._label, type(data)))
295 if self._element_types and len(self._element_types) != len(data):
296 raise SchemaException(
297 'Number of elements in tuple %r does not match that defined '
298 'in Tuple schema %r' % (str(data), self._label))
299 for data, element_type in zip(data, self._element_types):
300 element_type.Validate(data)
301
302
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800303class AnyOf(BaseType):
304 """AnyOf schema class.
305
306 Used to match any schemas in the given list of schemas.
307
308 Attributes:
309 label: A human-readable string to describe this object.
310 type_list: A list of Schema objects to be matched.
311 """
312 def __init__(self, label, type_list):
313 super(AnyOf, self).__init__(label)
314 if not isinstance(type_list, list):
315 raise SchemaException(
316 'type_list of AnyOf %r should be a list of Schema types' %
317 self._label)
318 self._type_list = type_list
319
320 def Validate(self, data):
321 """Validates if the given data matches any schema in type_list
322
323 Args:
324 data: A Python data structue to be validated.
325 """
326 match = False
327 for schema_type in self._type_list:
328 try:
329 schema_type.Validate(data)
330 except SchemaException:
331 continue
332 match = True
333 break
334 if not match:
335 raise SchemaException('%r does not match any type in %r' %
336 (data, self._label))