def compile(self, obj: object) -> Extractor: """`obj` should be a mapping or a string.""" if isinstance(obj, str): return self.compile({_KEY_JQ: obj}) if not isinstance(obj, Mapping): message = f"Must be a map or a string, given {type(obj)}" raise CompilationError(message) key_candidates = self._factory_map.keys() & obj.keys() key_count = len(key_candidates) if key_count != 1: raise CompilationError( f"Must have only 1 extraction key, but has {key_count}") factory_key = next(iter(key_candidates)) factory = self._factory_map[factory_key] query = obj[factory_key] multiple_obj = obj.get(_KEY_MULTIPLE, False) with on_key(_KEY_MULTIPLE): multiple = ensure_bool(multiple_obj) cast: Optional[Callable[[object], Any]] = None cast_obj = obj.get(_KEY_CAST_TO) if cast_obj is not None: with on_key(_KEY_CAST_TO): cast = self._compile_cast(cast_obj) return factory(query, multiple=multiple, cast=cast)
def compile_url_params( obj: object, arguments: Optional[Arguments] = None, ) -> UrlParams: """ Compiles an object into URL parameters. Args: obj: A compiled object, which should be a mapping or a string. arguments: Arguments to inject. Returns: The result of compilation. Raises: CompilationError: when compilation fails. """ obj = inject_arguments(obj, arguments) if isinstance(obj, str): return obj if not isinstance(obj, Mapping): raise CompilationError("Must be a mapping or a string") compiled = {} for key, value in obj.items(): assert isinstance(key, str) # Satisfied in injecting arguments. with on_key(key): compiled[key] = compile_url_param(value) return compiled
def compile( self, obj: object, arguments: Optional[Arguments] = None, ) -> RequestBodyCompiled: """ Compiles an object into an intermediate request body. Args: obj: A compiled object, which should be a mapping. arguments: Arguments to inject. Returns: The result of compilation. Raises: CompilationError: when compilation fails. """ obj = inject_arguments(obj, arguments) obj = ensure_mapping(obj) compiled = self._default type_obj = obj.get(_KEY_TYPE) if type_obj is not None: with on_key(_KEY_TYPE): key = ensure_str(type_obj) factory = _TYPE_FACTORY_MAP.get(key) if not factory: raise CompilationError( f"Must be in {list(_TYPE_FACTORY_MAP)}" f", but given: {key}") compiled = self._default.replace(factory()) return compiled.compile_and_replace(obj)
def compile(self, obj: object) -> MatcherFactory: """ Compile an object into a matcher factory. Args: obj: A compiled object. Returns: The result of compilation. Raises: CompilationError: when compilation fails. """ if isinstance(obj, str) and obj in self._static: return self._static[obj] if isinstance(obj, Mapping): if len(obj) != 1: message = f"Must have only 1 element, but has {len(obj)}" raise CompilationError(message) key, value_obj = next(iter(obj.items())) if key in self._taking_value: return self._compile_taking_value(key, value_obj) if key in self._recursive: return self._compile_recursive(key, value_obj) return ValueMatcherFactory(hamcrest.equal_to, StaticValue(obj))
def _compile_method(obj: object) -> Method: key = ensure_str(obj).upper() method = _METHOD_MAP.get(key) if not method: message = f"Must be in {list(_METHOD_MAP)}, but given: {obj}" raise CompilationError(message) return method
def test_response_compilation_fails(compiler: CaseCompiler, res): res.compile.side_effect = CompilationError("msg", node=NamedNode("bar")) with raises(CompilationError) as error_info: compiler.compile({"response": "res"}) assert error_info.value.path == [NamedNode("response"), NamedNode("bar")] res.compile.assert_called_once_with("res")
def test_request_compilation_fails(compiler: CaseCompiler, req): req.compile.side_effect = CompilationError("msg", node=NamedNode("foo")) with raises(CompilationError) as error_info: compiler.compile({"request": "/path"}) assert error_info.value.path == [NamedNode("request"), NamedNode("foo")] req.compile.assert_called_once_with("/path")
def test_conditions_compilation_fails(compiler: CaseCompiler, desc): desc.compile.side_effect = CompilationError("msg", node=NamedNode("foo")) with raises(CompilationError) as error_info: compiler.compile({"when": "xxx"}) assert error_info.value.path == [NamedNode("when"), IndexedNode(0), NamedNode("foo")] desc.compile.assert_called_once_with("xxx")
def test_given_invalid_body(compiler: RequestCompiler, body): body.compile.side_effect = CompilationError("x", node=IndexedNode(1)) with raises(CompilationError) as error_info: compiler.compile({"body": sentinel.body_obj}) assert error_info.value.path == [NamedNode("body"), IndexedNode(1)] body.compile.assert_called_once_with(sentinel.body_obj, None)
def test_when_parameter_compilation_fails(compiler: ScenarioCompiler, mocker): compile_parameter = mocker.patch(f"{PKG}.compile_parameter") compile_parameter.side_effect = CompilationError("message") with raises(CompilationError) as error_info: compiler.compile({"parameters": [sentinel.param_obj]}) assert error_info.value.path == [NamedNode("parameters"), IndexedNode(0)] compile_parameter.assert_called_once_with(sentinel.param_obj)
def test_map_compile_for_failing_func(): child_error = CompilationError("message", node=NamedNode("key")) failing_func = Mock(side_effect=[1, child_error, 2]) results = map_compile(failing_func, [3, 4, 5]) assert next(results) == 1 with raises(CompilationError) as error_info: next(results) assert error_info.value.path == [IndexedNode(1), NamedNode("key")] failing_func.assert_has_calls([call(3), call(4)])
def _compile_cast(obj: object) -> Callable[[object], Any]: """`obj` should be a string.""" key = ensure_str(obj) cast = _CAST_FUNC_MAP.get(key) if not cast: raise CompilationError(f"Invalid value: {key}") return cast
def test_given_an_invalid_params(compiler: RequestCompiler, mocker): compile_params = mocker.patch(f"{PKG}.compile_url_params") compile_params.side_effect = CompilationError("msg", node=NamedNode("x")) with raises(CompilationError) as error_info: compiler.compile({"params": sentinel.params}) assert error_info.value.path == [NamedNode("params"), NamedNode("x")] compile_params.assert_called_once_with(sentinel.params, None)
def test_compile_and_replace_given_invalid_data(mocker): compile_params = mocker.patch(f"{PKG}.compile_url_params") compile_params.side_effect = CompilationError("m", node=NamedNode("x")) default = UrlencodedRequestBodyCompiled(data=sentinel.original_data) with raises(CompilationError) as error_info: default.compile_and_replace({"data": sentinel.data}) assert error_info.value.path == [NamedNode("data"), NamedNode("x")] compile_params.assert_called_once_with(sentinel.data)
def test_render_path(): error = CompilationError("message") assert error.path == [] assert error.render_path() == "" assert render_path(error.path) == "" error = error.on_node(IndexedNode(1)) assert error.path == [IndexedNode(1)] assert error.render_path() == "[1]" assert render_path(error.path) == "[1]" error = CompilationError("message", node=None, child=error) assert error.path == [IndexedNode(1)] assert error.render_path() == "[1]" assert render_path(error.path) == "[1]" error = error.on_node(NamedNode("foo")) assert error.path == [NamedNode("foo"), IndexedNode(1)] assert error.render_path() == ".foo[1]" assert render_path(error.path) == ".foo[1]"
def compile_url_param(value: object) -> UrlParam: if value is None: return value if isinstance(value, list): return list(map_compile(compile_url_param_value, value)) try: return compile_url_param_value(value) except CompilationError as error: raise CompilationError( f"Not allowed type for a request parameter: {value.__class__}", cause=error, )
def ensure_bool(obj: object) -> bool: """ Ensure a boolean object. Args: obj: An ensured object, which should be a `bool` value. Returns: The compiled value. Raises: CompilationError: when compilation fails. """ if not isinstance(obj, bool): raise CompilationError(f"Must be a boolean, given {type(obj)}") return obj
def ensure_mapping(obj: object) -> Mapping: """ Ensure a mapping object. Args: obj: A compiled object, which should be a mapping. Returns: The compile result. Raises: CompilationError: when compilation fails. """ if not isinstance(obj, Mapping): raise CompilationError(f"Must be a map, given {type(obj)}") return obj
def ensure_str(obj: object) -> str: """ Ensure a string object. Args: obj: An ensured object, which should be a `string` value. Returns: The compiled value. Raises: CompilationError: when compilation fails. """ if not isinstance(obj, str): raise CompilationError(f"must be a string, given {type(obj)}") return obj
def compile_url_param_value(value: object) -> object: if isinstance(value, Value): return value if value is None: return value if isinstance(value, bool): return value if isinstance(value, int): return value if isinstance(value, float): return value if isinstance(value, str): return value if isinstance(value, date): return value raise CompilationError( f"Not allowed type for a request parameter value: {value.__class__}")
def _func(value): if value == "_error": raise CompilationError("message")
def _compile(flag: bool) -> bool: if not flag: raise CompilationError("msg", node=NamedNode("x")) return not flag
def _func(key: object, value: object) -> object: if not isinstance(key, str): message = f"Key must be a string, given {type(key)}: {key}" raise CompilationError(message) with on_key(key): return run_recursively(func, value)