async def _execute_call( self, kwargs: Dict[str, str], http_method: str, config: common.UrlMethod, message: Dict[str, Any], request_headers: Mapping[str, str], auth: Optional[MutableMapping[str, str]] = None, ) -> common.Response: try: if kwargs is None or config is None: raise Exception("This method is unknown! This should not occur!") # create message that contains all arguments message.update(kwargs) if config.properties.validate_sid: if "sid" not in message: raise exceptions.BadRequest("this is an agent to server call, it should contain an agent session id") elif not self.validate_sid(uuid.UUID(message["sid"])): raise exceptions.BadRequest("the sid %s is not valid." % message["sid"]) arguments = CallArguments(config.properties, message, request_headers) await arguments.process() authorize_request(auth, arguments.metadata, arguments.call_args, config) # rename arguments if handler requests this call_args = arguments.call_args if hasattr(config.handler, "__protocol_mapping__"): for k, v in config.handler.__protocol_mapping__.items(): if v in call_args: call_args[k] = call_args[v] del call_args[v] LOGGER.debug( "Calling method %s(%s)", config.handler, ", ".join(["%s='%s'" % (name, common.shorten(str(value))) for name, value in arguments.call_args.items()]), ) result = await config.handler(**arguments.call_args) return await arguments.process_return(config, result) except pydantic.ValidationError: LOGGER.exception(f"The handler {config.handler} caused a validation error in a data model (pydantic).") raise exceptions.ServerError("data validation error.") except exceptions.BaseHttpException: LOGGER.debug("An HTTP Error occurred", exc_info=True) raise except Exception as e: LOGGER.exception("An exception occured during the request.") raise exceptions.ServerError(str(e.args))
def _validate_union_return(self, arg_type: Type, value: Any) -> None: """Validate a return with a union type :see: protocol.common.MethodProperties._validate_function_types """ matching_type = None for t in typing_inspect.get_args(arg_type, evaluate=True): instanceof_type = t if typing_inspect.is_generic_type(t): instanceof_type = typing_inspect.get_origin(t) if isinstance(value, instanceof_type): if matching_type is not None: raise exceptions.ServerError( f"Return type is defined as a union {arg_type} for which multiple " f"types match the provided value {value}" ) matching_type = t if matching_type is None: raise exceptions.BadRequest( f"Invalid return value, no matching type found in union {arg_type} for value type {type(value)}" ) if typing_inspect.is_generic_type(matching_type): self._validate_generic_return(arg_type, matching_type)
def _validate_generic_return(self, arg_type: Type, value: Any) -> None: """Validate List or Dict types. :note: we return any here because the calling function also returns any. """ if issubclass(typing_inspect.get_origin(arg_type), list): if not isinstance(value, list): raise exceptions.ServerError( f"Invalid return value, type needs to be a list. Argument type should be {arg_type}" ) el_type = typing_inspect.get_args(arg_type, evaluate=True)[0] if el_type is Any: return for el in value: if typing_inspect.is_union_type(el_type): self._validate_union_return(el_type, el) elif not isinstance(el, el_type): raise exceptions.ServerError(f"Element {el} of returned list is not of type {el_type}.") elif issubclass(typing_inspect.get_origin(arg_type), dict): if not isinstance(value, dict): raise exceptions.ServerError( f"Invalid return value, type needs to be a dict. Argument type should be {arg_type}" ) el_type = typing_inspect.get_args(arg_type, evaluate=True)[1] if el_type is Any: return for k, v in value.items(): if not isinstance(k, str): raise exceptions.ServerError("Keys of return dict need to be strings.") if typing_inspect.is_union_type(el_type): self._validate_union_return(el_type, v) elif not isinstance(v, el_type): raise exceptions.ServerError(f"Element {v} of returned list is not of type {el_type}.") else: # This should not happen because of MethodProperties validation raise exceptions.BadRequest( f"Failed to validate generic type {arg_type} of return value, only List and Dict are supported" )
def _encode_body(self, body: ReturnTypes, content_type: str) -> Union[str, bytes]: if content_type == common.JSON_CONTENT: return common.json_encode(body) if content_type == common.HTML_CONTENT: assert isinstance(body, str) return body.encode(common.HTML_ENCODING) if content_type == common.HTML_CONTENT_WITH_UTF8_CHARSET: assert isinstance(body, str) return body.encode(common.UTF8_ENCODING) elif not isinstance(body, (str, bytes)): raise exceptions.ServerError( f"Body should be str or bytes and not {type(body)}." " For dict make sure content type is set to {common.JSON_CONTENT}" ) return body
async def get_server_status(self) -> StatusResponse: product_metadata = self.feature_manager.get_product_metadata() if product_metadata.version is None: raise exceptions.ServerError( "Could not find version number for the inmanta compiler." "Is inmanta installed? Use setuptools install or setuptools dev to install." ) slices = [] for slice_name, slice in self._server.get_slices().items(): try: slices.append( SliceStatus(name=slice_name, status=await slice.get_status())) except Exception: LOGGER.error( f"The following error occured while trying to determine the status of slice {slice_name}", exc_info=True, ) response = StatusResponse( product=product_metadata.product, edition=product_metadata.edition, version=product_metadata.version, license=product_metadata.license, extensions=self.get_extension_statuses( list(self._server.get_slices().values())), slices=slices, features=[ FeatureStatus(slice=feature.slice, name=feature.name, value=self.feature_manager.get_value(feature)) for feature in self.feature_manager.get_features() ], ) return response
async def process_return(self, config: common.UrlMethod, result: Apireturn) -> common.Response: """A handler can return ApiReturn, so lets handle all possible return types and convert it to a Response Apireturn = Union[int, Tuple[int, Optional[JsonType]], "ReturnValue", "BaseModel"] """ if "return" in self._argspec.annotations: # new style with return type return_type = self._argspec.annotations["return"] if return_type is None: if result is not None: raise exceptions.ServerError(f"Method {config.method_name} returned a result but is defined as -> None") return common.Response.create(ReturnValue(status_code=200, response=None), envelope=False) # There is no obvious method to check if the return_type is a specific version of the generic ReturnValue # The way this is implemented in typing is different for python 3.6 and 3.7. In this code we "trust" that the # signature of the handler and the method definition matches and the returned value matches this return value # Both isubclass and isinstance fail on this type # This check needs to be first because isinstance fails on generic types. # TODO: also validate the value inside a ReturnValue if return_type is Any: return common.Response.create( ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key ) elif typing_inspect.is_union_type(return_type): self._validate_union_return(return_type, result) return common.Response.create( ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key ) if typing_inspect.is_generic_type(return_type): if isinstance(result, ReturnValue): return common.Response.create(result, config.properties.envelope, config.properties.envelope_key) else: self._validate_generic_return(return_type, result) return common.Response.create( ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key ) elif isinstance(result, BaseModel): return common.Response.create( ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key ) elif isinstance(result, common.VALID_SIMPLE_ARG_TYPES): return common.Response.create( ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key ) else: raise exceptions.ServerError( f"Method {config.method_name} returned an invalid result {result} instead of a BaseModel or ReturnValue" ) else: # "old" style method definition if isinstance(result, tuple): if len(result) == 2: code, body = result else: raise exceptions.ServerError("Handlers for method call can only return a status code and a reply") elif isinstance(result, int): code = result body = None else: raise exceptions.ServerError( f"Method {config.method_name} returned an invalid result {result} instead of a status code or tupple" ) if body is not None: if config.properties.reply: return common.Response.create( ReturnValue(status_code=code, response=body), config.properties.envelope, config.properties.envelope_key, ) else: LOGGER.warning("Method %s returned a result although it has no reply!") return common.Response.create(ReturnValue(status_code=code, response=None), envelope=False)