%PDF- %PDF-
Direktori : /usr/lib/python3/dist-packages/landscape/lib/ |
Current File : //usr/lib/python3/dist-packages/landscape/lib/schema.py |
"""A schema system. Yes. Another one!""" from twisted.python.compat import iteritems from twisted.python.compat import long from twisted.python.compat import unicode class InvalidError(Exception): """Raised when invalid input is received.""" pass class Constant: """Something that must be equal to a constant value.""" def __init__(self, value): self.value = value def coerce(self, value): if isinstance(self.value, str) and isinstance(value, bytes): try: value = value.decode() except UnicodeDecodeError: pass if value != self.value: raise InvalidError(f"{value!r} != {self.value!r}") return value class Any: """Something which must apply to any of a number of different schemas. @param schemas: Other schema objects. """ def __init__(self, *schemas): self.schemas = schemas def coerce(self, value): """ The result of the first schema which doesn't raise L{InvalidError} from its C{coerce} method will be returned. """ for schema in self.schemas: try: return schema.coerce(value) except InvalidError: pass raise InvalidError( f"{value!r} did not match any schema in {self.schemas}", ) class Bool: """Something that must be a C{bool}.""" def coerce(self, value): if not isinstance(value, bool): raise InvalidError(f"{value!r} is not a bool") return value class Int: """Something that must be an C{int} or C{long}.""" def coerce(self, value): if not isinstance(value, (int, long)): raise InvalidError(f"{value!r} isn't an int or long") return value class Float: """Something that must be an C{int}, C{long}, or C{float}.""" def coerce(self, value): if not isinstance(value, (int, long, float)): raise InvalidError(f"{value!r} isn't a float") return value class Bytes: """A binary string. If the value is a Python3 str (unicode), it will be automatically encoded. """ def coerce(self, value): if isinstance(value, bytes): return value if isinstance(value, str): return value.encode() raise InvalidError(f"{value!r} isn't a bytestring") class Unicode: """Something that must be a C{unicode}. If the value is a C{str}, it will automatically be decoded. @param encoding: The encoding to automatically decode C{str}s with. """ def __init__(self, encoding="utf-8"): self.encoding = encoding def coerce(self, value): if isinstance(value, bytes): try: value = value.decode(self.encoding) except UnicodeDecodeError as e: raise InvalidError( "{!r} can't be decoded: {}".format(value, str(e)), ) if not isinstance(value, unicode): raise InvalidError(f"{value!r} isn't a unicode") return value class List: """Something which must be a C{list}. @param schema: The schema that all values of the list must match. """ def __init__(self, schema): self.schema = schema def coerce(self, value): if not isinstance(value, list): raise InvalidError(f"{value!r} is not a list") new_list = list(value) for i, subvalue in enumerate(value): try: new_list[i] = self.schema.coerce(subvalue) except InvalidError as e: raise InvalidError( f"{subvalue!r} could not coerce with {self.schema}: {e}", ) return new_list class Tuple: """Something which must be a fixed-length tuple. @param schema: A sequence of schemas, which will be applied to each value in the tuple respectively. """ def __init__(self, *schema): self.schema = schema def coerce(self, value): if not isinstance(value, tuple): raise InvalidError(f"{value!r} is not a tuple") if len(value) != len(self.schema): raise InvalidError( f"Need {len(self.schema)} items, " f"got {len(value)} in {value!r}", ) new_value = [] for schema, value in zip(self.schema, value): new_value.append(schema.coerce(value)) return tuple(new_value) class KeyDict: """Something which must be a C{dict} with defined keys. The keys must be constant and the values must match a per-key schema. If strict, extra keys cause an exception during coercion. @param schema: A dict mapping keys to schemas that the values of those keys must match. """ def __init__(self, schema, optional=None, strict=True): if optional is None: optional = [] self.optional = set(optional) self.schema = schema self._strict = strict def coerce(self, value): new_dict = {} if not isinstance(value, dict): raise InvalidError(f"{value!r} is not a dict.") for k, v in iteritems(value): unknown_key = k not in self.schema if unknown_key and self._strict: raise InvalidError( f"{k!r} is not a valid key as per {self.schema!r}", ) elif unknown_key: # We are in non-strict mode, so we ignore unknown keys. continue try: new_dict[k] = self.schema[k].coerce(v) except InvalidError as e: raise InvalidError( f"Value of {k!r} key of dict {value!r} could not coerce " f"with {self.schema[k]}: {e}", ) new_keys = set(new_dict.keys()) required_keys = set(self.schema.keys()) - self.optional missing = required_keys - new_keys if missing: raise InvalidError(f"Missing keys {missing}") return new_dict class Dict: """Something which must be a C{dict} with arbitrary keys. @param key_schema: The schema that keys must match. @param value_schema: The schema that values must match. """ def __init__(self, key_schema, value_schema): self.key_schema = key_schema self.value_schema = value_schema def coerce(self, value): if not isinstance(value, dict): raise InvalidError(f"{value!r} is not a dict.") new_dict = {} for k, v in value.items(): new_dict[self.key_schema.coerce(k)] = self.value_schema.coerce(v) return new_dict