blob: 7b8a7f96906a9803582275f731919a81ee34bcf4 [file] [log] [blame]
Hung-Te Lin1990b742017-08-09 17:34:57 +08001# Copyright 2012 The Chromium OS Authors. All rights reserved.
Ricky Liang1b72ccf2013-03-01 10:23:01 +08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Peter Shih86430492018-02-26 14:51:58 +08005# pylint: disable=protected-access,redefined-builtin
Ricky Liang1b72ccf2013-03-01 10:23:01 +08006
7"""A function to create a schema tree from the given schema expression.
8
9For example:
10
11 1. This is the schema of the encoded_fields in component database.
12
13 Dict('encoded_fields', Scalar('encoded_field', str),
14 Dict('encoded_indices', Scalar('encoded_index', int),
15 Dict('component_classes', Scalar('component_class', str),
16 AnyOf('component_names', [
17 Scalar('component_name', str),
18 List('list_of_component_names', Scalar('component_name', str)),
19 Scalar('none', type(None))
20 ])
21 )
22 )
23 )
24
25 2. This is the schema of the pattern in component database.
26
27 List('pattern',
28 Dict('pattern_field', key_type=Scalar('encoded_index', str),
29 value_type=Scalar('bit_offset', int))
30 )
31
32 3. This is the schema of the components in component database.
33
34 Dict('components', Scalar('component_class', str),
35 Dict('component_names', Scalar('component_name', str),
36 FixedDict('component_attributes',
37 items={
38 'value': AnyOf('probed_value', [
39 Scalar('probed_value', str),
40 List('list_of_probed_values', Scalar('probed_value', str))
41 ])
42 },
43 optional_items={
44 'labels': List('list_of_labels', Scalar('label', str))
45 }
46 )
47 )
48 )
49"""
50
51import copy
Yilin Yangea784662019-09-26 13:51:03 +080052
53from six import iteritems
54
Joel Kitchinga8be7962016-06-08 17:35:01 +080055from .type_utils import MakeList
Dean Liao452c6ee2013-03-11 16:23:33 +080056
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080057# To simplify portability issues, validating JSON schema is optional.
58try:
Peter Shih533566a2018-09-05 17:48:03 +080059 # pylint: disable=wrong-import-order
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080060 import jsonschema
61 _HAVE_JSONSCHEMA = True
62except ImportError:
63 _HAVE_JSONSCHEMA = False
64
Ricky Liang1b72ccf2013-03-01 10:23:01 +080065
66class SchemaException(Exception):
67 pass
68
69
70class BaseType(object):
71 """Base type class for schema classes.
72 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080073
Ricky Liang1b72ccf2013-03-01 10:23:01 +080074 def __init__(self, label):
Yong Hong3532ae82017-12-29 16:05:46 +080075 self.label = label
Ricky Liang1b72ccf2013-03-01 10:23:01 +080076
Dean Liao604e62b2013-03-11 19:12:50 +080077 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +080078 return 'BaseType(%r)' % self.label
Dean Liao604e62b2013-03-11 19:12:50 +080079
Ricky Liang1b72ccf2013-03-01 10:23:01 +080080 def Validate(self, data):
81 raise NotImplementedError
82
83
84class Scalar(BaseType):
85 """Scalar schema class.
86
87 Attributes:
88 label: A human-readable string to describe this Scalar.
89 element_type: The Python type of this Scalar. Cannot be a iterable type.
Jon Salz05fffde2014-07-14 12:56:47 +080090 choices: A set of allowable choices for the scalar, or None to allow
91 any values of the given type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +080092
93 Raises:
94 SchemaException if argument format is incorrect.
95 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080096
Jon Salz05fffde2014-07-14 12:56:47 +080097 def __init__(self, label, element_type, choices=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +080098 super(Scalar, self).__init__(label)
Yilin Yang4d4bcab2019-11-15 13:37:28 +080099 if getattr(element_type, '__iter__', None) and element_type not in (
100 str, bytes):
Dean Liao604e62b2013-03-11 19:12:50 +0800101 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800102 'element_type %r of Scalar %r is not a scalar type' % (element_type,
103 label))
Yong Hong3532ae82017-12-29 16:05:46 +0800104 self.element_type = element_type
105 self.choices = set(choices) if choices else None
Jon Salz05fffde2014-07-14 12:56:47 +0800106
Dean Liao604e62b2013-03-11 19:12:50 +0800107 def __repr__(self):
Jon Salz05fffde2014-07-14 12:56:47 +0800108 return 'Scalar(%r, %r%s)' % (
Yong Hong3532ae82017-12-29 16:05:46 +0800109 self.label, self.element_type,
110 ', choices=%r' % sorted(self.choices) if self.choices else '')
Dean Liao604e62b2013-03-11 19:12:50 +0800111
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800112 def Validate(self, data):
113 """Validates the given data against the Scalar schema.
114
115 It checks if the data's type matches the Scalar's element type. Also, it
116 checks if the data's value matches the Scalar's value if the required value
117 is specified.
118
119 Args:
120 data: A Python data structure to be validated.
121
122 Raises:
123 SchemaException if validation fails.
124 """
Yong Hong3532ae82017-12-29 16:05:46 +0800125 if not isinstance(data, self.element_type):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800126 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800127 (data, self.element_type, type(data)))
128 if self.choices and data not in self.choices:
Jon Salz05fffde2014-07-14 12:56:47 +0800129 raise SchemaException('Value mismatch on %r: expected one of %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800130 (data, sorted(self.choices)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800131
132
Yong Hong5961ad32019-05-14 17:43:18 +0800133class RegexpStr(Scalar):
134 """Schema class for a string which matches the specific regular expression.
135
136 Attributes:
137 label: A human-readable string to describe this Scalar.
138 regexp: A regular expression object to match.
139
140 Raises:
141 SchemaException if argument format is incorrect.
142 """
143
144 def __init__(self, label, regexp):
145 super(RegexpStr, self).__init__(label, str)
146 self.regexp = regexp
147
148 def __repr__(self):
149 return 'RegexpStr(%r, %s)' % (self.label, self.regexp.pattern)
150
151 def __deepcopy__(self, memo):
152 return RegexpStr(self.label, self.regexp)
153
154 def Validate(self, data):
155 """Validates the given data against the RegexpStr schema.
156
157 It first checks if the data's type is `str`. Then, it checks if the
158 value matches the regular expression.
159
160 Args:
161 data: A Python data structure to be validated.
162
163 Raises:
164 SchemaException if validation fails.
165 """
166 super(RegexpStr, self).Validate(data)
167 if not self.regexp.match(data):
168 raise SchemaException("Value %r doesn't match regeular expression %s" %
169 (data, self.regexp.pattern))
170
171
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800172class Dict(BaseType):
173 """Dict schema class.
174
175 This schema class is used to verify simple dict. Only the key type and value
176 type are validated.
177
178 Attributes:
179 label: A human-readable string to describe this Scalar.
Ricky Liangf5386b32013-03-11 16:40:45 +0800180 key_type: A schema object indicating the schema of the keys of this Dict. It
181 can be a Scalar or an AnyOf with possible values being all Scalars.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800182 value_type: A schema object indicating the schema of the values of this
183 Dict.
Yong Hong3532ae82017-12-29 16:05:46 +0800184 min_size: The minimum size of the elements, default to 0.
185 max_size: None or the maximum size of the elements.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800186
187 Raises:
188 SchemaException if argument format is incorrect.
189 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800190
Yong Hong3532ae82017-12-29 16:05:46 +0800191 def __init__(self, label, key_type, value_type, min_size=0, max_size=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800192 super(Dict, self).__init__(label)
Ricky Liangf5386b32013-03-11 16:40:45 +0800193 if not (isinstance(key_type, Scalar) or
Hung-Te Lin56b18402015-01-16 14:52:30 +0800194 (isinstance(key_type, AnyOf) and
195 key_type.CheckTypeOfPossibleValues(Scalar))):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800196 raise SchemaException('key_type %r of Dict %r is not Scalar' %
Yong Hong3532ae82017-12-29 16:05:46 +0800197 (key_type, self.label))
198 self.key_type = key_type
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800199 if not isinstance(value_type, BaseType):
200 raise SchemaException('value_type %r of Dict %r is not Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800201 (value_type, self.label))
202 self.value_type = value_type
203 self.min_size = min_size
204 self.max_size = max_size
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800205
Dean Liao604e62b2013-03-11 19:12:50 +0800206 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800207 size_expr = '[%d, %s]' % (self.min_size, ('inf' if self.max_size is None
208 else '%d' % self.max_size))
209 return 'Dict(%r, key_type=%r, value_type=%r, size=%s)' % (
210 self.label, self.key_type, self.value_type, size_expr)
Dean Liao604e62b2013-03-11 19:12:50 +0800211
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800212 def Validate(self, data):
213 """Validates the given data against the Dict schema.
214
215 It checks that all the keys in data matches the schema defined by key_type,
216 and all the values in data matches the schema defined by value_type.
217
218 Args:
219 data: A Python data structure to be validated.
220
221 Raises:
222 SchemaException if validation fails.
223 """
224 if not isinstance(data, dict):
225 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800226 (self.label, type(data)))
227
228 if len(data) < self.min_size:
229 raise SchemaException('Size mismatch on %r: expected size >= %r' %
230 (self.label, self.min_size))
231
232 if self.max_size is not None and self.max_size < len(data):
233 raise SchemaException('Size mismatch on %r: expected size <= %r' %
234 (self.label, self.max_size))
235
Yilin Yangea784662019-09-26 13:51:03 +0800236 for k, v in iteritems(data):
Yong Hong3532ae82017-12-29 16:05:46 +0800237 self.key_type.Validate(k)
238 self.value_type.Validate(v)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800239
240
241class FixedDict(BaseType):
242 """FixedDict schema class.
243
244 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
245 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
246 in XML.
247
248 An example FixedDict schema:
249 FixedDict('foo',
250 items={
251 'a': Scalar('bar', str),
252 'b': Scalar('buz', int)
253 }, optional_items={
254 'c': Scalar('boo', int)
255 })
256
257 Attributes:
258 label: A human-readable string to describe this dict.
259 items: A dict of required items that must be specified.
260 optional_items: A dict of optional items.
Yong Hong5961ad32019-05-14 17:43:18 +0800261 allow_undefined_keys: A boolean that indicates whether additional items
262 that is not recorded in both `items` and `optional_items` are allowed
263 or not.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800264
265 Raises:
266 SchemaException if argument format is incorrect.
267 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800268
Yong Hong5961ad32019-05-14 17:43:18 +0800269 def __init__(self, label, items=None, optional_items=None,
270 allow_undefined_keys=False):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800271 super(FixedDict, self).__init__(label)
272 if items and not isinstance(items, dict):
273 raise SchemaException('items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800274 self.label)
275 self.items = copy.deepcopy(items) if items is not None else {}
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800276 if optional_items and not isinstance(optional_items, dict):
277 raise SchemaException('optional_items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800278 self.label)
279 self.optional_items = (
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800280 copy.deepcopy(optional_items) if optional_items is not None else {})
Yong Hong5961ad32019-05-14 17:43:18 +0800281 self.allow_undefined_keys = allow_undefined_keys
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800282
Dean Liao604e62b2013-03-11 19:12:50 +0800283 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800284 return 'FixedDict(%r, items=%r, optional_items=%r)' % (self.label,
285 self.items,
286 self.optional_items)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800287
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800288 def Validate(self, data):
289 """Validates the given data and all its key-value pairs against the Dict
290 schema.
291
292 If a key of Dict's type is required, then it must exist in the data's keys.
Yong Hong5961ad32019-05-14 17:43:18 +0800293 If `self.allow_undefined_keys` is `False` and some items in the given data
294 are not in either `self.items` or `self.optional_items`, the method will
295 raise `SchemaException`.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800296
297 Args:
298 data: A Python data structure to be validated.
299
300 Raises:
301 SchemaException if validation fails.
302 """
303 if not isinstance(data, dict):
304 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800305 (self.label, type(data)))
Yilin Yang78fa12e2019-09-25 14:21:10 +0800306 data_key_list = list(data)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800307 # Check that every key-value pair in items exists in data
Yilin Yangea784662019-09-26 13:51:03 +0800308 for key, value_schema in iteritems(self.items):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800309 if key not in data:
310 raise SchemaException(
311 'Required item %r does not exist in FixedDict %r' %
312 (key, data))
313 value_schema.Validate(data[key])
314 data_key_list.remove(key)
315 # Check that all the remaining unmatched key-value pairs matches any
316 # definition in items or optional_items.
Yilin Yangea784662019-09-26 13:51:03 +0800317 for key, value_schema in iteritems(self.optional_items):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800318 if key not in data:
319 continue
320 value_schema.Validate(data[key])
321 data_key_list.remove(key)
Yong Hong5961ad32019-05-14 17:43:18 +0800322 if not self.allow_undefined_keys and data_key_list:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800323 raise SchemaException('Keys %r are undefined in FixedDict %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800324 (data_key_list, self.label))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800325
326
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800327class JSONSchemaDict(BaseType):
328 """JSON schema class.
329
330 This schema class allows mixing JSON schema with other schema types.
331
332 Attributes:
333 label: A human-readable string to describe this JSON schema.
334 schema: a JSON schema object.
335
336 Raises:
Fei Shao186d25b2018-11-09 16:55:48 +0800337 SchemaException if given schema is invalid (SchemaError) or fail
338 to validate data using the schema (ValidationError).
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800339 """
340 def __init__(self, label, schema):
341 super(JSONSchemaDict, self).__init__(label)
Yong Hong3532ae82017-12-29 16:05:46 +0800342 self.label = label
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800343 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800344 try:
345 jsonschema.Draft4Validator.check_schema(schema)
346 except Exception as e:
347 raise SchemaException('Schema %r is invalid: %r' % (schema, e))
Yong Hong3532ae82017-12-29 16:05:46 +0800348 self.schema = schema
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800349
350 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800351 return 'JSONSchemaDict(%r, %r)' % (self.label, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800352
353 def Validate(self, data):
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800354 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800355 try:
356 jsonschema.validate(data, self.schema)
357 except Exception as e:
358 raise SchemaException('Fail to validate %r with JSON schema %r: %r' %
359 (data, self.schema, e))
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800360
361
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800362class List(BaseType):
363 """List schema class.
364
365 Attributes:
366 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800367 element_type: Optional schema object to validate the elements of the list.
368 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800369 min_length: The expected minimum length of the list. Default to 0.
370 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800371
372 Raises:
373 SchemaException if argument format is incorrect.
374 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800375
Yong Hong3532ae82017-12-29 16:05:46 +0800376 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800377 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800378 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800379 raise SchemaException(
380 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800381 (element_type, self.label))
382 self.element_type = copy.deepcopy(element_type)
383 self.min_length = min_length
384 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800385
Dean Liao604e62b2013-03-11 19:12:50 +0800386 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800387 max_bound_repr = ('inf' if self.max_length is None
388 else '%d' % self.max_length)
389 return 'List(%r, %r, [%r, %s])' % (
390 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800391
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800392 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800393 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800394
395 Args:
396 data: A Python data structure to be validated.
397
398 Raises:
399 SchemaException if validation fails.
400 """
401 if not isinstance(data, list):
402 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800403 (self.label, type(data)))
404
405 if len(data) < self.min_length:
406 raise SchemaException('Length mismatch on %r: expected length >= %d' %
407 (self.label, self.min_length))
408
409 if self.max_length is not None and self.max_length < len(data):
410 raise SchemaException('Length mismatch on %r: expected length <= %d' %
411 (self.label, self.max_length))
412
413 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800414 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800415 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800416
417
Ricky Liang3e5342b2013-03-08 12:16:25 +0800418class Tuple(BaseType):
419 """Tuple schema class.
420
421 Comparing to List, the Tuple schema makes sure that every element exactly
422 matches the defined position and schema.
423
424 Attributes:
425 label: A string to describe this tuple.
426 element_types: Optional list or tuple schema object to describe the
427 types of the Tuple.
428
429 Raises:
430 SchemaException if argument format is incorrect.
431 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800432
Ricky Liang3e5342b2013-03-08 12:16:25 +0800433 def __init__(self, label, element_types=None):
434 super(Tuple, self).__init__(label)
435 if (element_types and
436 (not isinstance(element_types, (tuple, list))) or
437 (not all([isinstance(x, BaseType)] for x in element_types))):
438 raise SchemaException(
439 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800440 (element_types, self.label))
441 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800442
Dean Liao604e62b2013-03-11 19:12:50 +0800443 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800444 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800445
Ricky Liang3e5342b2013-03-08 12:16:25 +0800446 def Validate(self, data):
447 """Validates the given data and all its elements against the Tuple schema.
448
449 Args:
450 data: A Python data structure to be validated.
451
452 Raises:
453 SchemaException if validation fails.
454 """
455 if not isinstance(data, tuple):
456 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800457 (self.label, type(data)))
458 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800459 raise SchemaException(
460 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800461 'in Tuple schema %r' % (str(data), self.label))
Yilin Yangd3d97b02020-01-14 16:46:33 +0800462 for content, element_type in zip(data, self.element_types):
463 element_type.Validate(content)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800464
465
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800466class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800467 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800468
469 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800470 types: A list of Schema objects to be matched.
471 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800472 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800473
Dean Liao604e62b2013-03-11 19:12:50 +0800474 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800475 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800476 if (not isinstance(types, list) or
477 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800478 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800479 'types in AnyOf(types=%r%s) should be a list of Schemas' %
480 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800481 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800482
483 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800484 label = '' if self.label is None else ', label=%r' % self.label
485 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800486
Ricky Liangf5386b32013-03-11 16:40:45 +0800487 def CheckTypeOfPossibleValues(self, schema_type):
488 """Checks if the acceptable types are of the same type as schema_type.
489
490 Args:
491 schema_type: The schema type to check against with.
492 """
Yong Hong3532ae82017-12-29 16:05:46 +0800493 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800494
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800495 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800496 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800497
498 Args:
499 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800500
501 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800502 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800503 """
504 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800505 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800506 try:
507 schema_type.Validate(data)
508 except SchemaException:
509 continue
510 match = True
511 break
512 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800513 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800514 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800515
516
517class Optional(AnyOf):
518 """A Schema class which accepts either None or given Schemas.
519
520 It is a special case of AnyOf class: in addition of given schema(s), it also
521 accepts None.
522
523 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800524 types: A (or a list of) Schema object(s) to be matched.
525 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800526 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800527
Dean Liao604e62b2013-03-11 19:12:50 +0800528 def __init__(self, types, label=None):
529 try:
530 super(Optional, self).__init__(MakeList(types), label=label)
531 except SchemaException:
532 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800533 'types in Optional(types=%r%s) should be a Schema or a list of '
534 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800535
536 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800537 label = '' if self.label is None else ', label=%r' % self.label
538 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800539
540 def Validate(self, data):
541 """Validates if the given data is None or matches any schema in types.
542
543 Args:
544 data: A Python data structue to be validated.
545
546 Raises:
547 SchemaException if data is not None and no schemas in types validates the
548 input data.
549 """
550 if data is None:
551 return
552 try:
553 super(Optional, self).Validate(data)
554 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800555 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800556 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800557 self.types))