blob: 81005da4d89559f751112650f0e78002d7bb7591 [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
5# pylint: disable=W0212, W0622
6
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:
56 import jsonschema
57 _HAVE_JSONSCHEMA = True
58except ImportError:
59 _HAVE_JSONSCHEMA = False
60
Ricky Liang1b72ccf2013-03-01 10:23:01 +080061
62class SchemaException(Exception):
63 pass
64
65
66class BaseType(object):
67 """Base type class for schema classes.
68 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080069
Ricky Liang1b72ccf2013-03-01 10:23:01 +080070 def __init__(self, label):
Yong Hong3532ae82017-12-29 16:05:46 +080071 self.label = label
Ricky Liang1b72ccf2013-03-01 10:23:01 +080072
Dean Liao604e62b2013-03-11 19:12:50 +080073 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +080074 return 'BaseType(%r)' % self.label
Dean Liao604e62b2013-03-11 19:12:50 +080075
Ricky Liang1b72ccf2013-03-01 10:23:01 +080076 def Validate(self, data):
77 raise NotImplementedError
78
79
80class Scalar(BaseType):
81 """Scalar schema class.
82
83 Attributes:
84 label: A human-readable string to describe this Scalar.
85 element_type: The Python type of this Scalar. Cannot be a iterable type.
Jon Salz05fffde2014-07-14 12:56:47 +080086 choices: A set of allowable choices for the scalar, or None to allow
87 any values of the given type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +080088
89 Raises:
90 SchemaException if argument format is incorrect.
91 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080092
Jon Salz05fffde2014-07-14 12:56:47 +080093 def __init__(self, label, element_type, choices=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +080094 super(Scalar, self).__init__(label)
95 if getattr(element_type, '__iter__', None):
Dean Liao604e62b2013-03-11 19:12:50 +080096 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +080097 'element_type %r of Scalar %r is not a scalar type' % (element_type,
98 label))
Yong Hong3532ae82017-12-29 16:05:46 +080099 self.element_type = element_type
100 self.choices = set(choices) if choices else None
Jon Salz05fffde2014-07-14 12:56:47 +0800101
Dean Liao604e62b2013-03-11 19:12:50 +0800102 def __repr__(self):
Jon Salz05fffde2014-07-14 12:56:47 +0800103 return 'Scalar(%r, %r%s)' % (
Yong Hong3532ae82017-12-29 16:05:46 +0800104 self.label, self.element_type,
105 ', choices=%r' % sorted(self.choices) if self.choices else '')
Dean Liao604e62b2013-03-11 19:12:50 +0800106
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800107 def Validate(self, data):
108 """Validates the given data against the Scalar schema.
109
110 It checks if the data's type matches the Scalar's element type. Also, it
111 checks if the data's value matches the Scalar's value if the required value
112 is specified.
113
114 Args:
115 data: A Python data structure to be validated.
116
117 Raises:
118 SchemaException if validation fails.
119 """
Yong Hong3532ae82017-12-29 16:05:46 +0800120 if not isinstance(data, self.element_type):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800121 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800122 (data, self.element_type, type(data)))
123 if self.choices and data not in self.choices:
Jon Salz05fffde2014-07-14 12:56:47 +0800124 raise SchemaException('Value mismatch on %r: expected one of %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800125 (data, sorted(self.choices)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800126
127
128class Dict(BaseType):
129 """Dict schema class.
130
131 This schema class is used to verify simple dict. Only the key type and value
132 type are validated.
133
134 Attributes:
135 label: A human-readable string to describe this Scalar.
Ricky Liangf5386b32013-03-11 16:40:45 +0800136 key_type: A schema object indicating the schema of the keys of this Dict. It
137 can be a Scalar or an AnyOf with possible values being all Scalars.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800138 value_type: A schema object indicating the schema of the values of this
139 Dict.
Yong Hong3532ae82017-12-29 16:05:46 +0800140 min_size: The minimum size of the elements, default to 0.
141 max_size: None or the maximum size of the elements.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800142
143 Raises:
144 SchemaException if argument format is incorrect.
145 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800146
Yong Hong3532ae82017-12-29 16:05:46 +0800147 def __init__(self, label, key_type, value_type, min_size=0, max_size=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800148 super(Dict, self).__init__(label)
Ricky Liangf5386b32013-03-11 16:40:45 +0800149 if not (isinstance(key_type, Scalar) or
Hung-Te Lin56b18402015-01-16 14:52:30 +0800150 (isinstance(key_type, AnyOf) and
151 key_type.CheckTypeOfPossibleValues(Scalar))):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800152 raise SchemaException('key_type %r of Dict %r is not Scalar' %
Yong Hong3532ae82017-12-29 16:05:46 +0800153 (key_type, self.label))
154 self.key_type = key_type
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800155 if not isinstance(value_type, BaseType):
156 raise SchemaException('value_type %r of Dict %r is not Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800157 (value_type, self.label))
158 self.value_type = value_type
159 self.min_size = min_size
160 self.max_size = max_size
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800161
Dean Liao604e62b2013-03-11 19:12:50 +0800162 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800163 size_expr = '[%d, %s]' % (self.min_size, ('inf' if self.max_size is None
164 else '%d' % self.max_size))
165 return 'Dict(%r, key_type=%r, value_type=%r, size=%s)' % (
166 self.label, self.key_type, self.value_type, size_expr)
Dean Liao604e62b2013-03-11 19:12:50 +0800167
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800168 def Validate(self, data):
169 """Validates the given data against the Dict schema.
170
171 It checks that all the keys in data matches the schema defined by key_type,
172 and all the values in data matches the schema defined by value_type.
173
174 Args:
175 data: A Python data structure to be validated.
176
177 Raises:
178 SchemaException if validation fails.
179 """
180 if not isinstance(data, dict):
181 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800182 (self.label, type(data)))
183
184 if len(data) < self.min_size:
185 raise SchemaException('Size mismatch on %r: expected size >= %r' %
186 (self.label, self.min_size))
187
188 if self.max_size is not None and self.max_size < len(data):
189 raise SchemaException('Size mismatch on %r: expected size <= %r' %
190 (self.label, self.max_size))
191
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800192 for k, v in data.iteritems():
Yong Hong3532ae82017-12-29 16:05:46 +0800193 self.key_type.Validate(k)
194 self.value_type.Validate(v)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800195
196
197class FixedDict(BaseType):
198 """FixedDict schema class.
199
200 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
201 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
202 in XML.
203
204 An example FixedDict schema:
205 FixedDict('foo',
206 items={
207 'a': Scalar('bar', str),
208 'b': Scalar('buz', int)
209 }, optional_items={
210 'c': Scalar('boo', int)
211 })
212
213 Attributes:
214 label: A human-readable string to describe this dict.
215 items: A dict of required items that must be specified.
216 optional_items: A dict of optional items.
217
218 Raises:
219 SchemaException if argument format is incorrect.
220 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800221
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800222 def __init__(self, label, items=None, optional_items=None):
223 super(FixedDict, self).__init__(label)
224 if items and not isinstance(items, dict):
225 raise SchemaException('items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800226 self.label)
227 self.items = copy.deepcopy(items) if items is not None else {}
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800228 if optional_items and not isinstance(optional_items, dict):
229 raise SchemaException('optional_items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800230 self.label)
231 self.optional_items = (
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800232 copy.deepcopy(optional_items) if optional_items is not None else {})
233
Dean Liao604e62b2013-03-11 19:12:50 +0800234 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800235 return 'FixedDict(%r, items=%r, optional_items=%r)' % (self.label,
236 self.items,
237 self.optional_items)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800238
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800239 def Validate(self, data):
240 """Validates the given data and all its key-value pairs against the Dict
241 schema.
242
243 If a key of Dict's type is required, then it must exist in the data's keys.
244
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, dict):
252 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800253 (self.label, type(data)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800254 data_key_list = data.keys()
255 # Check that every key-value pair in items exists in data
Yong Hong3532ae82017-12-29 16:05:46 +0800256 for key, value_schema in self.items.iteritems():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800257 if key not in data:
258 raise SchemaException(
259 'Required item %r does not exist in FixedDict %r' %
260 (key, data))
261 value_schema.Validate(data[key])
262 data_key_list.remove(key)
263 # Check that all the remaining unmatched key-value pairs matches any
264 # definition in items or optional_items.
Yong Hong3532ae82017-12-29 16:05:46 +0800265 for key, value_schema in self.optional_items.iteritems():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800266 if key not in data:
267 continue
268 value_schema.Validate(data[key])
269 data_key_list.remove(key)
270 if data_key_list:
271 raise SchemaException('Keys %r are undefined in FixedDict %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800272 (data_key_list, self.label))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800273
274
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800275class JSONSchemaDict(BaseType):
276 """JSON schema class.
277
278 This schema class allows mixing JSON schema with other schema types.
279
280 Attributes:
281 label: A human-readable string to describe this JSON schema.
282 schema: a JSON schema object.
283
284 Raises:
285 SchemaException if given schema is invalid.
286 ValidationError if argument format is incorrect.
287 """
288 def __init__(self, label, schema):
289 super(JSONSchemaDict, self).__init__(label)
Yong Hong3532ae82017-12-29 16:05:46 +0800290 self.label = label
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800291 if _HAVE_JSONSCHEMA:
292 jsonschema.Draft4Validator.check_schema(schema)
Yong Hong3532ae82017-12-29 16:05:46 +0800293 self.schema = schema
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800294
295 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800296 return 'JSONSchemaDict(%r, %r)' % (self.label, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800297
298 def Validate(self, data):
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800299 if _HAVE_JSONSCHEMA:
Yong Hong3532ae82017-12-29 16:05:46 +0800300 jsonschema.validate(data, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800301
302
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800303class List(BaseType):
304 """List schema class.
305
306 Attributes:
307 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800308 element_type: Optional schema object to validate the elements of the list.
309 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800310 min_length: The expected minimum length of the list. Default to 0.
311 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800312
313 Raises:
314 SchemaException if argument format is incorrect.
315 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800316
Yong Hong3532ae82017-12-29 16:05:46 +0800317 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800318 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800319 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800320 raise SchemaException(
321 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800322 (element_type, self.label))
323 self.element_type = copy.deepcopy(element_type)
324 self.min_length = min_length
325 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800326
Dean Liao604e62b2013-03-11 19:12:50 +0800327 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800328 max_bound_repr = ('inf' if self.max_length is None
329 else '%d' % self.max_length)
330 return 'List(%r, %r, [%r, %s])' % (
331 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800332
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800333 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800334 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800335
336 Args:
337 data: A Python data structure to be validated.
338
339 Raises:
340 SchemaException if validation fails.
341 """
342 if not isinstance(data, list):
343 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800344 (self.label, type(data)))
345
346 if len(data) < self.min_length:
347 raise SchemaException('Length mismatch on %r: expected length >= %d' %
348 (self.label, self.min_length))
349
350 if self.max_length is not None and self.max_length < len(data):
351 raise SchemaException('Length mismatch on %r: expected length <= %d' %
352 (self.label, self.max_length))
353
354 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800355 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800356 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800357
358
Ricky Liang3e5342b2013-03-08 12:16:25 +0800359class Tuple(BaseType):
360 """Tuple schema class.
361
362 Comparing to List, the Tuple schema makes sure that every element exactly
363 matches the defined position and schema.
364
365 Attributes:
366 label: A string to describe this tuple.
367 element_types: Optional list or tuple schema object to describe the
368 types of the Tuple.
369
370 Raises:
371 SchemaException if argument format is incorrect.
372 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800373
Ricky Liang3e5342b2013-03-08 12:16:25 +0800374 def __init__(self, label, element_types=None):
375 super(Tuple, self).__init__(label)
376 if (element_types and
377 (not isinstance(element_types, (tuple, list))) or
378 (not all([isinstance(x, BaseType)] for x in element_types))):
379 raise SchemaException(
380 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800381 (element_types, self.label))
382 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800383
Dean Liao604e62b2013-03-11 19:12:50 +0800384 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800385 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800386
Ricky Liang3e5342b2013-03-08 12:16:25 +0800387 def Validate(self, data):
388 """Validates the given data and all its elements against the Tuple schema.
389
390 Args:
391 data: A Python data structure to be validated.
392
393 Raises:
394 SchemaException if validation fails.
395 """
396 if not isinstance(data, tuple):
397 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800398 (self.label, type(data)))
399 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800400 raise SchemaException(
401 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800402 'in Tuple schema %r' % (str(data), self.label))
403 for data, element_type in zip(data, self.element_types):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800404 element_type.Validate(data)
405
406
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800407class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800408 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800409
410 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800411 types: A list of Schema objects to be matched.
412 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800413 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800414
Dean Liao604e62b2013-03-11 19:12:50 +0800415 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800416 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800417 if (not isinstance(types, list) or
418 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800419 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800420 'types in AnyOf(types=%r%s) should be a list of Schemas' %
421 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800422 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800423
424 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800425 label = '' if self.label is None else ', label=%r' % self.label
426 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800427
Ricky Liangf5386b32013-03-11 16:40:45 +0800428 def CheckTypeOfPossibleValues(self, schema_type):
429 """Checks if the acceptable types are of the same type as schema_type.
430
431 Args:
432 schema_type: The schema type to check against with.
433 """
Yong Hong3532ae82017-12-29 16:05:46 +0800434 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800435
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800436 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800437 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800438
439 Args:
440 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800441
442 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800443 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800444 """
445 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800446 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800447 try:
448 schema_type.Validate(data)
449 except SchemaException:
450 continue
451 match = True
452 break
453 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800454 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800455 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800456
457
458class Optional(AnyOf):
459 """A Schema class which accepts either None or given Schemas.
460
461 It is a special case of AnyOf class: in addition of given schema(s), it also
462 accepts None.
463
464 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800465 types: A (or a list of) Schema object(s) to be matched.
466 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800467 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800468
Dean Liao604e62b2013-03-11 19:12:50 +0800469 def __init__(self, types, label=None):
470 try:
471 super(Optional, self).__init__(MakeList(types), label=label)
472 except SchemaException:
473 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800474 'types in Optional(types=%r%s) should be a Schema or a list of '
475 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800476
477 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800478 label = '' if self.label is None else ', label=%r' % self.label
479 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800480
481 def Validate(self, data):
482 """Validates if the given data is None or matches any schema in types.
483
484 Args:
485 data: A Python data structue to be validated.
486
487 Raises:
488 SchemaException if data is not None and no schemas in types validates the
489 input data.
490 """
491 if data is None:
492 return
493 try:
494 super(Optional, self).Validate(data)
495 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800496 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800497 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800498 self.types))