schema.py (3166B)
1 from typing import Any, Callable, cast, Dict, Sequence, Set, Type, TypeVar, Union 2 3 T = TypeVar("T") 4 5 def validate_dict(obj: Any, required_keys: Set[str] = set(), optional_keys: Set[str] = set()) -> None: 6 """ 7 Validates the keys for a particular object 8 This logic ensures: 9 1. the obj is type dict 10 2. That at a minimum the provided required_keys are present. 11 Additionally, the logic checks for a set of optional_keys. With those two 12 sets of keys, the logic will raise an error if there are extra keys in obj. 13 :param obj: The object that will be checked. 14 :param required_keys: Set of required keys that the obj should have. 15 :param optional_keys: Set of optional keys that the obj should have. 16 :return: `None` if obj does not have any extra keys. 17 :raises ValueError: If there unexpected keys or missing required keys. 18 """ 19 if not isinstance(obj, dict): 20 raise ValueError(f"Object is not a dictionary. Input: {obj}") 21 extra_keys = set(obj.keys()) - required_keys - optional_keys 22 missing_required_keys = required_keys - set(obj.keys()) 23 if extra_keys: 24 raise ValueError(f"Object contains invalid keys: {sorted(extra_keys)}") 25 if missing_required_keys: 26 raise ValueError(f"Object missing required keys: {sorted(missing_required_keys)}") 27 28 29 class SchemaValue(): 30 """ 31 Set of helpers to convert raw input into an expected value for a given schema 32 """ 33 @staticmethod 34 def from_dict(x: Any) -> Dict[str, Any]: 35 if not isinstance(x, dict): 36 raise ValueError(f"Input value {x} is not a dict") 37 keys = x.keys() 38 for key in keys: 39 if not isinstance(key, str): 40 raise ValueError(f"Input value {x} contains key {key} that is not a string") 41 return cast(Dict[str, Any], x) 42 43 44 @staticmethod 45 def from_str(x: Any) -> str: 46 if not isinstance(x, str): 47 raise ValueError(f"Input value {x} is not a string") 48 return x 49 50 51 @staticmethod 52 def from_none(x: Any) -> None: 53 if x is not None: 54 raise ValueError(f"Input value {x} is not none") 55 return x 56 57 58 @staticmethod 59 def from_union(fs: 60 Sequence[Union[ 61 Callable[[Any], Sequence[T]], 62 Callable[[Any], T], 63 ]], 64 x: Any) -> Any: 65 for f in fs: 66 try: 67 return f(x) 68 except Exception: 69 pass 70 raise ValueError(f"Input value {x} does not fit one of the expected values for the union") 71 72 73 @staticmethod 74 def from_list(f: Callable[[Any], T], x: Any) -> Sequence[T]: 75 if not isinstance(x, list): 76 raise ValueError(f"Input value {x} is not a list") 77 return [f(y) for y in x] 78 79 80 @staticmethod 81 def from_class(cls: Type[T]) -> Callable[[Any], T]: 82 def class_converter(x: Any) -> T: 83 try: 84 # https://github.com/python/mypy/issues/10343 85 return cls(x) # type: ignore [call-arg] 86 except Exception: 87 raise ValueError(f"Input value {x} could not be converted to {cls}") 88 return class_converter