blob: 0115fdab2f894e2182f11940622710c0d31ae5d9 [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
Joel Kitchinga8be7962016-06-08 17:35:01 +080052from .type_utils import MakeList
Dean Liao452c6ee2013-03-11 16:23:33 +080053
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080054# To simplify portability issues, validating JSON schema is optional.
55try:
Peter Shih533566a2018-09-05 17:48:03 +080056 # pylint: disable=wrong-import-order
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080057 import jsonschema
58 _HAVE_JSONSCHEMA = True
59except ImportError:
60 _HAVE_JSONSCHEMA = False
61
Ricky Liang1b72ccf2013-03-01 10:23:01 +080062
63class SchemaException(Exception):
64 pass
65
66
67class BaseType(object):
68 """Base type class for schema classes.
69 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080070
Ricky Liang1b72ccf2013-03-01 10:23:01 +080071 def __init__(self, label):
Yong Hong3532ae82017-12-29 16:05:46 +080072 self.label = label
Ricky Liang1b72ccf2013-03-01 10:23:01 +080073
Dean Liao604e62b2013-03-11 19:12:50 +080074 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +080075 return 'BaseType(%r)' % self.label
Dean Liao604e62b2013-03-11 19:12:50 +080076
Ricky Liang1b72ccf2013-03-01 10:23:01 +080077 def Validate(self, data):
78 raise NotImplementedError
79
80
81class Scalar(BaseType):
82 """Scalar schema class.
83
84 Attributes:
85 label: A human-readable string to describe this Scalar.
86 element_type: The Python type of this Scalar. Cannot be a iterable type.
Jon Salz05fffde2014-07-14 12:56:47 +080087 choices: A set of allowable choices for the scalar, or None to allow
88 any values of the given type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +080089
90 Raises:
91 SchemaException if argument format is incorrect.
92 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080093
Jon Salz05fffde2014-07-14 12:56:47 +080094 def __init__(self, label, element_type, choices=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +080095 super(Scalar, self).__init__(label)
96 if getattr(element_type, '__iter__', None):
Dean Liao604e62b2013-03-11 19:12:50 +080097 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +080098 'element_type %r of Scalar %r is not a scalar type' % (element_type,
99 label))
Yong Hong3532ae82017-12-29 16:05:46 +0800100 self.element_type = element_type
101 self.choices = set(choices) if choices else None
Jon Salz05fffde2014-07-14 12:56:47 +0800102
Dean Liao604e62b2013-03-11 19:12:50 +0800103 def __repr__(self):
Jon Salz05fffde2014-07-14 12:56:47 +0800104 return 'Scalar(%r, %r%s)' % (
Yong Hong3532ae82017-12-29 16:05:46 +0800105 self.label, self.element_type,
106 ', choices=%r' % sorted(self.choices) if self.choices else '')
Dean Liao604e62b2013-03-11 19:12:50 +0800107
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800108 def Validate(self, data):
109 """Validates the given data against the Scalar schema.
110
111 It checks if the data's type matches the Scalar's element type. Also, it
112 checks if the data's value matches the Scalar's value if the required value
113 is specified.
114
115 Args:
116 data: A Python data structure to be validated.
117
118 Raises:
119 SchemaException if validation fails.
120 """
Yong Hong3532ae82017-12-29 16:05:46 +0800121 if not isinstance(data, self.element_type):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800122 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800123 (data, self.element_type, type(data)))
124 if self.choices and data not in self.choices:
Jon Salz05fffde2014-07-14 12:56:47 +0800125 raise SchemaException('Value mismatch on %r: expected one of %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800126 (data, sorted(self.choices)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800127
128
129class Dict(BaseType):
130 """Dict schema class.
131
132 This schema class is used to verify simple dict. Only the key type and value
133 type are validated.
134
135 Attributes:
136 label: A human-readable string to describe this Scalar.
Ricky Liangf5386b32013-03-11 16:40:45 +0800137 key_type: A schema object indicating the schema of the keys of this Dict. It
138 can be a Scalar or an AnyOf with possible values being all Scalars.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800139 value_type: A schema object indicating the schema of the values of this
140 Dict.
Yong Hong3532ae82017-12-29 16:05:46 +0800141 min_size: The minimum size of the elements, default to 0.
142 max_size: None or the maximum size of the elements.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800143
144 Raises:
145 SchemaException if argument format is incorrect.
146 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800147
Yong Hong3532ae82017-12-29 16:05:46 +0800148 def __init__(self, label, key_type, value_type, min_size=0, max_size=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800149 super(Dict, self).__init__(label)
Ricky Liangf5386b32013-03-11 16:40:45 +0800150 if not (isinstance(key_type, Scalar) or
Hung-Te Lin56b18402015-01-16 14:52:30 +0800151 (isinstance(key_type, AnyOf) and
152 key_type.CheckTypeOfPossibleValues(Scalar))):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800153 raise SchemaException('key_type %r of Dict %r is not Scalar' %
Yong Hong3532ae82017-12-29 16:05:46 +0800154 (key_type, self.label))
155 self.key_type = key_type
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800156 if not isinstance(value_type, BaseType):
157 raise SchemaException('value_type %r of Dict %r is not Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800158 (value_type, self.label))
159 self.value_type = value_type
160 self.min_size = min_size
161 self.max_size = max_size
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800162
Dean Liao604e62b2013-03-11 19:12:50 +0800163 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800164 size_expr = '[%d, %s]' % (self.min_size, ('inf' if self.max_size is None
165 else '%d' % self.max_size))
166 return 'Dict(%r, key_type=%r, value_type=%r, size=%s)' % (
167 self.label, self.key_type, self.value_type, size_expr)
Dean Liao604e62b2013-03-11 19:12:50 +0800168
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800169 def Validate(self, data):
170 """Validates the given data against the Dict schema.
171
172 It checks that all the keys in data matches the schema defined by key_type,
173 and all the values in data matches the schema defined by value_type.
174
175 Args:
176 data: A Python data structure to be validated.
177
178 Raises:
179 SchemaException if validation fails.
180 """
181 if not isinstance(data, dict):
182 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800183 (self.label, type(data)))
184
185 if len(data) < self.min_size:
186 raise SchemaException('Size mismatch on %r: expected size >= %r' %
187 (self.label, self.min_size))
188
189 if self.max_size is not None and self.max_size < len(data):
190 raise SchemaException('Size mismatch on %r: expected size <= %r' %
191 (self.label, self.max_size))
192
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800193 for k, v in data.iteritems():
Yong Hong3532ae82017-12-29 16:05:46 +0800194 self.key_type.Validate(k)
195 self.value_type.Validate(v)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800196
197
198class FixedDict(BaseType):
199 """FixedDict schema class.
200
201 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
202 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
203 in XML.
204
205 An example FixedDict schema:
206 FixedDict('foo',
207 items={
208 'a': Scalar('bar', str),
209 'b': Scalar('buz', int)
210 }, optional_items={
211 'c': Scalar('boo', int)
212 })
213
214 Attributes:
215 label: A human-readable string to describe this dict.
216 items: A dict of required items that must be specified.
217 optional_items: A dict of optional items.
218
219 Raises:
220 SchemaException if argument format is incorrect.
221 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800222
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800223 def __init__(self, label, items=None, optional_items=None):
224 super(FixedDict, self).__init__(label)
225 if items and not isinstance(items, dict):
226 raise SchemaException('items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800227 self.label)
228 self.items = copy.deepcopy(items) if items is not None else {}
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800229 if optional_items and not isinstance(optional_items, dict):
230 raise SchemaException('optional_items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800231 self.label)
232 self.optional_items = (
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800233 copy.deepcopy(optional_items) if optional_items is not None else {})
234
Dean Liao604e62b2013-03-11 19:12:50 +0800235 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800236 return 'FixedDict(%r, items=%r, optional_items=%r)' % (self.label,
237 self.items,
238 self.optional_items)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800239
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800240 def Validate(self, data):
241 """Validates the given data and all its key-value pairs against the Dict
242 schema.
243
244 If a key of Dict's type is required, then it must exist in the data's keys.
245
246 Args:
247 data: A Python data structure to be validated.
248
249 Raises:
250 SchemaException if validation fails.
251 """
252 if not isinstance(data, dict):
253 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800254 (self.label, type(data)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800255 data_key_list = data.keys()
256 # Check that every key-value pair in items exists in data
Yong Hong3532ae82017-12-29 16:05:46 +0800257 for key, value_schema in self.items.iteritems():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800258 if key not in data:
259 raise SchemaException(
260 'Required item %r does not exist in FixedDict %r' %
261 (key, data))
262 value_schema.Validate(data[key])
263 data_key_list.remove(key)
264 # Check that all the remaining unmatched key-value pairs matches any
265 # definition in items or optional_items.
Yong Hong3532ae82017-12-29 16:05:46 +0800266 for key, value_schema in self.optional_items.iteritems():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800267 if key not in data:
268 continue
269 value_schema.Validate(data[key])
270 data_key_list.remove(key)
271 if data_key_list:
272 raise SchemaException('Keys %r are undefined in FixedDict %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800273 (data_key_list, self.label))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800274
275
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800276class JSONSchemaDict(BaseType):
277 """JSON schema class.
278
279 This schema class allows mixing JSON schema with other schema types.
280
281 Attributes:
282 label: A human-readable string to describe this JSON schema.
283 schema: a JSON schema object.
284
285 Raises:
Fei Shao186d25b2018-11-09 16:55:48 +0800286 SchemaException if given schema is invalid (SchemaError) or fail
287 to validate data using the schema (ValidationError).
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800288 """
289 def __init__(self, label, schema):
290 super(JSONSchemaDict, self).__init__(label)
Yong Hong3532ae82017-12-29 16:05:46 +0800291 self.label = label
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800292 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800293 try:
294 jsonschema.Draft4Validator.check_schema(schema)
295 except Exception as e:
296 raise SchemaException('Schema %r is invalid: %r' % (schema, e))
Yong Hong3532ae82017-12-29 16:05:46 +0800297 self.schema = schema
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800298
299 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800300 return 'JSONSchemaDict(%r, %r)' % (self.label, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800301
302 def Validate(self, data):
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800303 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800304 try:
305 jsonschema.validate(data, self.schema)
306 except Exception as e:
307 raise SchemaException('Fail to validate %r with JSON schema %r: %r' %
308 (data, self.schema, e))
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800309
310
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800311class List(BaseType):
312 """List schema class.
313
314 Attributes:
315 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800316 element_type: Optional schema object to validate the elements of the list.
317 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800318 min_length: The expected minimum length of the list. Default to 0.
319 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800320
321 Raises:
322 SchemaException if argument format is incorrect.
323 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800324
Yong Hong3532ae82017-12-29 16:05:46 +0800325 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800326 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800327 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800328 raise SchemaException(
329 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800330 (element_type, self.label))
331 self.element_type = copy.deepcopy(element_type)
332 self.min_length = min_length
333 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800334
Dean Liao604e62b2013-03-11 19:12:50 +0800335 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800336 max_bound_repr = ('inf' if self.max_length is None
337 else '%d' % self.max_length)
338 return 'List(%r, %r, [%r, %s])' % (
339 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800340
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800341 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800342 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800343
344 Args:
345 data: A Python data structure to be validated.
346
347 Raises:
348 SchemaException if validation fails.
349 """
350 if not isinstance(data, list):
351 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800352 (self.label, type(data)))
353
354 if len(data) < self.min_length:
355 raise SchemaException('Length mismatch on %r: expected length >= %d' %
356 (self.label, self.min_length))
357
358 if self.max_length is not None and self.max_length < len(data):
359 raise SchemaException('Length mismatch on %r: expected length <= %d' %
360 (self.label, self.max_length))
361
362 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800363 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800364 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800365
366
Ricky Liang3e5342b2013-03-08 12:16:25 +0800367class Tuple(BaseType):
368 """Tuple schema class.
369
370 Comparing to List, the Tuple schema makes sure that every element exactly
371 matches the defined position and schema.
372
373 Attributes:
374 label: A string to describe this tuple.
375 element_types: Optional list or tuple schema object to describe the
376 types of the Tuple.
377
378 Raises:
379 SchemaException if argument format is incorrect.
380 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800381
Ricky Liang3e5342b2013-03-08 12:16:25 +0800382 def __init__(self, label, element_types=None):
383 super(Tuple, self).__init__(label)
384 if (element_types and
385 (not isinstance(element_types, (tuple, list))) or
386 (not all([isinstance(x, BaseType)] for x in element_types))):
387 raise SchemaException(
388 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800389 (element_types, self.label))
390 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800391
Dean Liao604e62b2013-03-11 19:12:50 +0800392 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800393 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800394
Ricky Liang3e5342b2013-03-08 12:16:25 +0800395 def Validate(self, data):
396 """Validates the given data and all its elements against the Tuple schema.
397
398 Args:
399 data: A Python data structure to be validated.
400
401 Raises:
402 SchemaException if validation fails.
403 """
404 if not isinstance(data, tuple):
405 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800406 (self.label, type(data)))
407 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800408 raise SchemaException(
409 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800410 'in Tuple schema %r' % (str(data), self.label))
411 for data, element_type in zip(data, self.element_types):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800412 element_type.Validate(data)
413
414
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800415class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800416 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800417
418 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800419 types: A list of Schema objects to be matched.
420 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800421 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800422
Dean Liao604e62b2013-03-11 19:12:50 +0800423 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800424 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800425 if (not isinstance(types, list) or
426 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800427 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800428 'types in AnyOf(types=%r%s) should be a list of Schemas' %
429 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800430 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800431
432 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800433 label = '' if self.label is None else ', label=%r' % self.label
434 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800435
Ricky Liangf5386b32013-03-11 16:40:45 +0800436 def CheckTypeOfPossibleValues(self, schema_type):
437 """Checks if the acceptable types are of the same type as schema_type.
438
439 Args:
440 schema_type: The schema type to check against with.
441 """
Yong Hong3532ae82017-12-29 16:05:46 +0800442 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800443
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800444 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800445 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800446
447 Args:
448 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800449
450 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800451 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800452 """
453 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800454 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800455 try:
456 schema_type.Validate(data)
457 except SchemaException:
458 continue
459 match = True
460 break
461 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800462 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800463 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800464
465
466class Optional(AnyOf):
467 """A Schema class which accepts either None or given Schemas.
468
469 It is a special case of AnyOf class: in addition of given schema(s), it also
470 accepts None.
471
472 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800473 types: A (or a list of) Schema object(s) to be matched.
474 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800475 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800476
Dean Liao604e62b2013-03-11 19:12:50 +0800477 def __init__(self, types, label=None):
478 try:
479 super(Optional, self).__init__(MakeList(types), label=label)
480 except SchemaException:
481 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800482 'types in Optional(types=%r%s) should be a Schema or a list of '
483 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800484
485 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800486 label = '' if self.label is None else ', label=%r' % self.label
487 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800488
489 def Validate(self, data):
490 """Validates if the given data is None or matches any schema in types.
491
492 Args:
493 data: A Python data structue to be validated.
494
495 Raises:
496 SchemaException if data is not None and no schemas in types validates the
497 input data.
498 """
499 if data is None:
500 return
501 try:
502 super(Optional, self).Validate(data)
503 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800504 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800505 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800506 self.types))