blob: 3c3d2512f7a4c5c578c138504c494f92cfefa3fd [file] [log] [blame]
# Copyright 2019 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
"""Helpers for using the `attr` library."""
def attr_type(type_, subname=''):
"""An `attr.s` validator for asserting the type of a value.
Essentially the same as `attr.validators.instance_of`, except that it allows
the provision of a 'subname' to provide a better error message when checking
a sub-field of the value.
Args:
* type_ (object) - The python type object to validate `value` against.
* subname (str) - Some sub-element of attrib.name; e.g. if checking
a dictionary key, this might be ' keys'.
Returns a validator function which raises TypeError if the value doesn't match
the value.
"""
def inner(_self, attrib, value):
if not isinstance(value, type_):
raise TypeError(
"'{name}' must be {type!r} (got {value!r} that is a "
"{actual!r}).".format(
name=attrib.name+subname,
type=type_,
actual=value.__class__,
value=value,
),
attrib,
value,
type_,
subname,
)
return inner
def attr_superclass(type_, subname=''):
"""An `attr.s` validator for asserting the superclass of a value.
Args:
* type_ (object) - The python type object to validate `value` is a subclass
of.
* subname (str) - Some sub-element of attrib.name; e.g. if checking
a dictionary key, this might be ' keys'.
Returns a validator function which raises TypeError if the value doesn't match
the value.
"""
def inner(_self, attrib, value):
if not issubclass(type(value), type_):
raise TypeError(
"'{name}' must be a subclass of {type!r} (got {value!r} that is a "
"{actual!r}).".format(
name=attrib.name+subname,
type=type_,
actual=value.__class__,
value=value,
),
attrib,
value,
type_,
subname,
)
return inner
def attr_dict_type(key_type, val_type, value_seq=False):
"""Helper function for writing attr.s validators for dictionary types.
Args:
* key_type (object) - The python type object to validate the dict's keys.
* val_type (object) - The python type object to validate the dict's
values.
* value_seq (bool) - If the dictionary maps to a sequence of val_type.
Returns a validator function which raises TypeError if:
* The value is not a dictionary
* All of it's keys don't match `key_type`
* All of it's values don't match `val_type`
"""
def inner(self, attrib, value):
# late import to avoid import cycle
from ..engine_types import FrozenDict
attr_type((dict, FrozenDict))(self, attrib, value)
for k, subval in value.items():
attr_type(key_type, ' keys')(self, attrib, k)
subname = '[%r]' % k
if value_seq:
attr_seq_type(val_type, subname)(self, attrib, subval)
else:
attr_type(val_type, subname)(self, attrib, subval)
return inner
def attr_seq_type(val_type, subname=''):
"""Helper function for writing attr.s validators for list types.
Args:
* val_type (object) - The python type object to validate the list's values.
Returns a validator function which raises TypeError if:
* The value is not a list, tuple, set or frozenset
* All of it's values don't match `val_type`
"""
def inner(self, attrib, value):
attr_type((list, tuple, set, frozenset), subname)(self, attrib, value)
for subval in value:
attr_type(val_type, subname + ' values')(self, attrib, subval)
return inner
def attr_list_type(val_type):
"""Helper function for writing attr.s validators for list types.
Args:
* val_type (object) - The python type object to validate the list's values.
Returns a validator function which raises TypeError if:
* The value is not a list
* All of it's values don't match `val_type`
"""
def inner(self, attrib, value):
attr_type((list, tuple))(self, attrib, value)
for subval in value:
attr_type(val_type, ' values')(self, attrib, subval)
return inner
def attr_value_is(msg, check_fn, subname=''):
"""Helper function for writing attr.s validators.
Args:
* msg (str) - The message to include in the ValueError. Should be
the quoted part of: name is not '<msg>'.
* check_fn (callable) - Called with 'value'; should return True iff the
value is valid.
* subname (str) - Some sub-element of attrib.name; e.g. if checking
a dictionary key, this might be ' keys'.
Returns a validator function which raises TypeError if the value doesn't match
the value.
"""
def inner(_self, attrib, value):
if not check_fn(value):
raise ValueError(
"'{name}' is not {msg} (got {value!r})".format(
name=attrib.name+subname,
msg=msg,
value=value,
),
attrib,
value,
msg,
subname,
)
return inner