"""Just enough of voluptuous for the integration's schemas.""" class Invalid(Exception): pass class _Marker: def __init__(self, key, default=None): self.key = key self.default = default def __hash__(self): return hash(self.key) def __eq__(self, other): return self.key == getattr(other, "key", other) class Required(_Marker): pass class Optional(_Marker): pass def _check(validator, value): if isinstance(validator, type): if not isinstance(value, validator): raise Invalid(f"expected {validator.__name__}") return value return validator(value) class Schema: def __init__(self, schema): self.schema = schema def __call__(self, data): result = {} known = set() for marker, validator in self.schema.items(): known.add(marker.key) if marker.key in data: result[marker.key] = _check(validator, data[marker.key]) elif isinstance(marker, Required): raise Invalid(f"required key not provided: {marker.key}") elif marker.default is not None: result[marker.key] = marker.default extra = set(data) - known if extra: raise Invalid(f"extra keys not allowed: {sorted(extra)}") return result class All: def __init__(self, *validators): self.validators = validators def __call__(self, value): for validator in self.validators: value = _check(validator, value) return value def In(options): def validate(value): if value not in options: raise Invalid(f"{value} not in {options}") return value return validate def Coerce(kind): def validate(value): try: return kind(value) except (TypeError, ValueError) as err: raise Invalid(str(err)) from err return validate def Range(min=None): def validate(value): if min is not None and value < min: raise Invalid(f"{value} below {min}") return value return validate def Length(min=None): def validate(value): if min is not None and len(value) < min: raise Invalid("too short") return value return validate