Source code for parameters.validators

"""
parameters.validators
=====================

A module implementing validation of ParameterSets against ParameterSchema.

Classes
-------

SchemaBase           - The base class of all "active" Schema objects to be placed in a ParameterSchema.
-> Sublass           - Validates the same-path ParameterSet value if it is of the specified type.
-> Eval              - Validates the same-path ParameterSet value if the provided expression
                       evaluates ("eval") to True.

ParameterSchema      - A sub-class of ParameterSet against which other ParameterSets
                       can be validated against.

CongruencyValidator  - A CongruencyValidator validates a ParameterSet against a ParameterSchema
                       via member "validate(parameter_set,parameter_schema)".

ValidationError      - The Exception raised when validation fails

Functions
---------

congruent_dicts      - returns True if two nested dictionaries have the same key heirarchy,
                       otherwise False.


See also: parameters

"""

from __future__ import absolute_import
import yaml
from parameters import ParameterSet
import parameters


[docs]class SchemaBase(object): """ The base class of all "active" `Schema` objects to be placed in a `ParameterSchema`. Schema objects define the "validate" member which accepts the to-be-validated `ParameterSet` value from the same path as the `Schema` object in the `ParameterSchema` and returns True if the value is valid, otherwise False. """ def __init__(self): pass
[docs] def validate(self, leaf): return False
def __repr__(self): cls = self.__class__ return '.'.join([cls.__module__, cls.__name__])+'()'
[docs]class Subclass(SchemaBase): """ To be used as a value in a `ParameterSchema`. Validates the same-path `ParameterSet` value if it is of the specified type. See also: `SchemaBase` """ def __init__(self, type=None): self.type = type def validate(self, leaf): return isinstance(leaf, self.type) def __repr__(self): cls = self.__class__ return '.'.join([cls.__module__, cls.__name__])+'(type=%s)' % (self.type.__name__) def __eq__(self, x): if isinstance(x, Subclass): return self.type == x.type else: return False
[docs]class Eval(SchemaBase): """ To be used as a value in a `ParameterSchema`. Validates the same-path `ParameterSet` value if the provided expression (with `leaf(value)` mapped to var in eval local namespace) evaluates to True. See also: `SchemaBase` """ def __init__(self, expr, var='leaf'): self.var = var self.expr = expr def validate(self, leaf): l = {} l[self.var] = leaf return eval(self.expr, {}, l) def __repr__(self): cls = self.__class__ return '.'.join([cls.__module__, cls.__name__])+'(%s,var=%s)' % (self.expr, self.var) def __eq__(self, x): if isinstance(x, Eval): return self.expr == x.expr and self.var == x.var else: return False # add all schema checkers to this list
schema_checkers = [Subclass, Eval] # create a namespace of schema_checkers schema_checkers_namespace = {} for x in schema_checkers: schema_checkers_namespace[x.__class__.__name__] = x
[docs]class ParameterSchema(ParameterSet): """ A sub-class of `ParameterSet` against which other ParameterSets can be validated. Presently, it is more or less a `ParameterSet`, with all leafs(values) which are not explicitly a subclass of the `SchemaBase` object replaced by a `Subclass(type=<leaf(value) type>)` instance. `ParameterSchema` may contain arbitrary `Schema` objects subclassed from `SchemaBase` which validate leafs by the member function `validate(leaf)` returning True or false if the given leaf in the `ParameterSet` at the same path should be validated or not, e.g.:: LambdaSchema('isinstance(x,str)',var='x'), *unimplemented* Timedate('%.2d-%.2m-%.2y'), etc. *unimplemented* Email() *unimplemented* Url() *unimplemented* File() *unimplemented* FileExists() etc. Example: >>> schema = ParameterSchema({'age': 0, 'height': Subclass(float)}) >>> # is equivalent to >>> schema = ParameterSchema({'age': Subclass(int), 'height': Subclass(float)}) See also: `SchemaBase`, `Eval`, `Subclass` """ def __init__(self, initializer): import types ps = initializer # try to create a ParameterSet from ps if # it is not one already if not isinstance(ps, ParameterSet): # create ParameterSet, but allowing SchemaBase derived objects ps = ParameterSet(ps, update_namespace=schema_checkers_namespace) # convert each element for x in ps.flat(): key = x[0] value = x[1] if isinstance(value, SchemaBase): self.flat_add(key, value) else: self.flat_add(key, Subclass(type=type(value)))
class ValidationError(Exception): """ Raised when `ParameterSchema` validation fails, and provides failure information See also: `CongruencyValidator`, `ParameterSchema` """ def __init__(self, path='', schema_base=None, parameter=None): self.path = path self.schema_base = schema_base self.parameter = parameter def __str__(self): return 'validation error @ %s: parameter "%s" failed against schema: %s' % (self.path, self.parameter, self.schema_base)
[docs]class CongruencyValidator(object): """ A `CongruencyValidator` validates a `ParameterSet` against a `ParameterSchema` either returning `True`, or raising a `ValidationError` with the path, `SchemaBase` subclass and parameter value for which the validation failed. The `CongruencyValidator` expects all names defined in the schema to be present in the parameter set and vice-versa, and will run validation for each item in the namespace tree. The validation functionality is available via the "validate" member `CongruencyValidator.validate(parameter_set, parameter_schema)` Example:: validator = CongruencyValidator() try: validator.validate(parameter_set,parameter_schema) except ValidationError, e: See also: `ParameterSet`, `ParameterSchema` """ def __init__(self): pass
[docs] def validate(self, parameter_set, parameter_schema): """ Validates a `ParameterSet` against a `ParameterSchema` either returning `True`, or raising a `ValidationError` with the path and `SchemaBase` subclass for which validation failed. Expects all names defined in the schema to be present in the parameter set and vice-versa, and will run validation for each item in the namespace tree. See also: `CongruencyValidator`. """ ps = parameter_set schema = parameter_schema ps_keys = set() schema_keys = set() for path, sb in schema.flat(): try: val = ps[path] except KeyError: raise ValidationError(path=path, schema_base=sb, parameter='<MISSING>') if not sb.validate(val): raise ValidationError(path=path, schema_base=sb, parameter=val) for path, val in ps.flat(): try: sb = schema[path] except KeyError: raise ValidationError(path=path, schema_base='<MISSING>', parameter=val) return True
def congruent_dicts(template, candidate, subset=False, parent_path=''): """Return True if d1 and d2 have same key heirarchy, otherwise False if subset=True, the key heirarchy of d2 maybe a subset """ dt = template dc = candidate # if one is a dict, and the other not, return False types = (isinstance(dt, dict), isinstance(dc, dict)) # both are not dicts, return True (for recursion) if all([type == False for type in types]): return True if all(types): # both are dicts dt_keys_set = set(dt.keys()) dc_keys_set = set(dc.keys()) missing_keys = dt_keys_set-dc_keys_set # keys in d2 which are unknown to d1 unknown_keys = dc_keys_set-dt_keys_set keys_intersection = dt_keys_set.intersection(dc_keys_set) # candidate has spurious keys if unknown_keys: return False # candidate is missing keys which are expected if not subset and missing_keys: return False # check that all sub dicts are congruent. subs_congruent = [congruent_dicts(template[key], candidate[key], subset) for key in keys_intersection] return all(subs_congruent) else: # inhomogeneousisms are underway # if subset, then return True if d1 is a dict (then d2 is not a dict) if subset and types[0]: return True # one is a dict, and the other not. return False # Add to parameters on import parameters.ParameterSchema = ParameterSchema parameters.Subclass = Subclass parameters.Eval = Eval parameters.SchemaBase = SchemaBase parameters.CongruencyValidator = CongruencyValidator parameters.ValidationError = ValidationError