From 1f939172b00f20f4a0df1749337218968cb5e034 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Thu, 25 Apr 2024 19:53:14 +0000 Subject: [PATCH] Techdebt: Refactor request-handling AWSLambda --- moto/awslambda/models.py | 20 +- moto/awslambda/responses.py | 452 +++++------------- moto/awslambda/urls.py | 104 +--- moto/core/responses.py | 7 +- .../test_lambda_eventsourcemapping.py | 10 +- 5 files changed, 177 insertions(+), 416 deletions(-) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index aeefc988b4b..8437195b225 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -739,7 +739,7 @@ def _get_layers_data(self, layers_versions_arns: List[str]) -> List[LayerDataTyp {"Arn": lv.arn, "CodeSize": lv.code_size} for lv in layer_versions if lv ] - def get_code_signing_config(self) -> Dict[str, Any]: + def get_function_code_signing_config(self) -> Dict[str, Any]: return { "CodeSigningConfigArn": self.code_signing_config_arn, "FunctionName": self.function_name, @@ -1408,7 +1408,7 @@ def create_from_cloudformation_json( # type: ignore[misc] ) -> "LambdaVersion": properties = cloudformation_json["Properties"] function_name = properties["FunctionName"] - func = lambda_backends[account_id][region_name].publish_function(function_name) + func = lambda_backends[account_id][region_name].publish_version(function_name) spec = {"Version": func.version} # type: ignore[union-attr] return LambdaVersion(spec) @@ -1662,7 +1662,7 @@ def put_function(self, fn: LambdaFunction) -> None: self._functions[fn.function_name] = {"latest": fn, "versions": []} self._arns[fn.function_arn] = fn - def publish_function( + def publish_version( self, name_or_arn: str, description: str = "" ) -> Optional[LambdaFunction]: function = self.get_function_by_name_or_arn_forbid_qualifier(name_or_arn) @@ -1944,7 +1944,7 @@ def create_function(self, spec: Dict[str, Any]) -> LambdaFunction: self._lambdas.put_function(fn) if spec.get("Publish"): - ver = self.publish_function(function_name) + ver = self.publish_version(function_name) fn = copy.deepcopy( fn ) # We don't want to change the actual version - just the return value @@ -2068,16 +2068,16 @@ def delete_layer_version(self, layer_name: str, layer_version: str) -> None: def get_layer_version(self, layer_name: str, layer_version: str) -> LayerVersion: return self._layers.get_layer_version(layer_name, layer_version) - def get_layer_versions(self, layer_name: str) -> Iterable[LayerVersion]: + def list_layer_versions(self, layer_name: str) -> Iterable[LayerVersion]: return self._layers.get_layer_versions(layer_name) def layers_versions_by_arn(self, layer_version_arn: str) -> Optional[LayerVersion]: return self._layers.get_layer_version_by_arn(layer_version_arn) - def publish_function( + def publish_version( self, function_name: str, description: str = "" ) -> Optional[LambdaFunction]: - return self._lambdas.publish_function(function_name, description) + return self._lambdas.publish_version(function_name, description) def get_function( self, function_name_or_arn: str, qualifier: Optional[str] = None @@ -2337,9 +2337,9 @@ def remove_permission( fn = self.get_function(function_name) fn.policy.del_statement(sid, revision) - def get_code_signing_config(self, function_name: str) -> Dict[str, Any]: + def get_function_code_signing_config(self, function_name: str) -> Dict[str, Any]: fn = self.get_function(function_name) - return fn.get_code_signing_config() + return fn.get_function_code_signing_config() def get_policy(self, function_name: str, qualifier: Optional[str] = None) -> str: fn = self._lambdas.get_function_by_name_or_arn_with_qualifier( @@ -2354,7 +2354,7 @@ def update_function_code( fn.update_function_code(body) if body.get("Publish", False): - fn = self.publish_function(function_name) # type: ignore[assignment] + fn = self.publish_version(function_name) # type: ignore[assignment] return fn.update_function_code(body) diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index 94245b399c9..9d4b8504a89 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -4,13 +4,9 @@ from urllib.parse import unquote from moto.core.responses import TYPE_RESPONSE, BaseResponse -from moto.core.utils import path_url from moto.utilities.aws_headers import amz_crc32 -from .exceptions import ( - FunctionAlreadyExists, - UnknownFunctionException, -) +from .exceptions import FunctionAlreadyExists, UnknownFunctionException from .models import LambdaBackend from .utils import get_backend @@ -27,131 +23,6 @@ def json_body(self) -> Dict[str, Any]: # type: ignore[misc] def backend(self) -> LambdaBackend: return get_backend(self.current_account, self.region) - def root(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - return self._list_functions() - elif request.method == "POST": - return self._create_function() - else: - raise ValueError("Cannot handle request") - - def event_source_mappings( - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - querystring = self.querystring - event_source_arn = querystring.get("EventSourceArn", [None])[0] - function_name = querystring.get("FunctionName", [None])[0] - return self._list_event_source_mappings(event_source_arn, function_name) - elif request.method == "POST": - return self._create_event_source_mapping() - else: - raise ValueError("Cannot handle request") - - def aliases(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return] - self.setup_class(request, full_url, headers) - - if request.method == "POST": - return self._create_alias() - elif request.method == "GET": - path = request.path if hasattr(request, "path") else path_url(request.url) - function_name = path.split("/")[-2] - return self._list_aliases(function_name) - - def alias(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return] - self.setup_class(request, full_url, headers) - if request.method == "DELETE": - return self._delete_alias() - elif request.method == "GET": - return self._get_alias() - elif request.method == "PUT": - return self._update_alias() - - def event_source_mapping( - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - path = request.path if hasattr(request, "path") else path_url(request.url) - uuid = path.split("/")[-1] - if request.method == "GET": - return self._get_event_source_mapping(uuid) - elif request.method == "PUT": - return self._update_event_source_mapping(uuid) - elif request.method == "DELETE": - return self._delete_event_source_mapping(uuid) - else: - raise ValueError("Cannot handle request") - - def list_layers(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return] - self.setup_class(request, full_url, headers) - if request.method == "GET": - return self._list_layers() - - def layers_version( # type: ignore[return] - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - layer_name = unquote(self.path.split("/")[-3]) - layer_version = self.path.split("/")[-1] - if request.method == "DELETE": - return self._delete_layer_version(layer_name, layer_version) - elif request.method == "GET": - return self._get_layer_version(layer_name, layer_version) - - def layers_versions( # type: ignore[return] - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - return self._get_layer_versions() - if request.method == "POST": - return self._publish_layer_version() - - def function(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - return self._get_function() - elif request.method == "DELETE": - return self._delete_function() - else: - raise ValueError("Cannot handle request") - - def versions(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - # This is ListVersionByFunction - - path = request.path if hasattr(request, "path") else path_url(request.url) - function_name = path.split("/")[-2] - return self._list_versions_by_function(function_name) - - elif request.method == "POST": - return self._publish_function() - else: - raise ValueError("Cannot handle request") - - @amz_crc32 - def invoke( - self, request: Any, full_url: str, headers: Any - ) -> Tuple[int, Dict[str, str], Union[str, bytes]]: - self.setup_class(request, full_url, headers) - if request.method == "POST": - return self._invoke(request) - else: - raise ValueError("Cannot handle request") - - @amz_crc32 - def invoke_async( - self, request: Any, full_url: str, headers: Any - ) -> Tuple[int, Dict[str, str], Union[str, bytes]]: - self.setup_class(request, full_url, headers) - if request.method == "POST": - return self._invoke_async() - else: - raise ValueError("Cannot handle request") - def tag(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: self.setup_class(request, full_url, headers) if request.method == "GET": @@ -163,97 +34,30 @@ def tag(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: else: raise ValueError(f"Cannot handle {request.method} request") - def policy(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - return self._get_policy(request) - elif request.method == "POST": - return self._add_policy(request) - elif request.method == "DELETE": - return self._del_policy(request, self.querystring) - else: - raise ValueError(f"Cannot handle {request.method} request") - - def configuration(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "PUT": - return self._put_configuration() - if request.method == "GET": - return self._get_function_configuration() - else: - raise ValueError("Cannot handle request") - - def code(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "PUT": - return self._put_code() - else: - raise ValueError("Cannot handle request") - - def code_signing_config( # type: ignore[return] - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) - if request.method == "GET": - return self._get_code_signing_config() - - def function_concurrency( - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - http_method = request.method - self.setup_class(request, full_url, headers) - - if http_method == "GET": - return self._get_function_concurrency() - elif http_method == "DELETE": - return self._delete_function_concurrency() - elif http_method == "PUT": - return self._put_function_concurrency() - else: - raise ValueError("Cannot handle request") - - def function_url_config( # type: ignore[return] - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - http_method = request.method - self.setup_class(request, full_url, headers) - - if http_method == "DELETE": - return self._delete_function_url_config() - elif http_method == "GET": - return self._get_function_url_config() - elif http_method == "POST": - return self._create_function_url_config() - elif http_method == "PUT": - return self._update_function_url_config() - - def _add_policy(self, request: Any) -> TYPE_RESPONSE: - path = request.path if hasattr(request, "path") else path_url(request.url) - function_name = unquote(path.split("/")[-2]) + def add_permission(self) -> str: + function_name = unquote(self.path.split("/")[-2]) qualifier = self.querystring.get("Qualifier", [None])[0] statement = self.body statement = self.backend.add_permission(function_name, qualifier, statement) - return 200, {}, json.dumps({"Statement": json.dumps(statement)}) + return json.dumps({"Statement": json.dumps(statement)}) - def _get_policy(self, request: Any) -> TYPE_RESPONSE: - path = request.path if hasattr(request, "path") else path_url(request.url) - function_name = unquote(path.split("/")[-2]) + def get_policy(self) -> str: + function_name = unquote(self.path.split("/")[-2]) qualifier = self.querystring.get("Qualifier", [None])[0] - out = self.backend.get_policy(function_name, qualifier) - return 200, {}, out - - def _del_policy(self, request: Any, querystring: Dict[str, Any]) -> TYPE_RESPONSE: - path = request.path if hasattr(request, "path") else path_url(request.url) - function_name = unquote(path.split("/")[-3]) - statement_id = path.split("/")[-1].split("?")[0] - revision = querystring.get("RevisionId", "") + return self.backend.get_policy(function_name, qualifier) + + def remove_permission(self) -> TYPE_RESPONSE: + function_name = unquote(self.path.split("/")[-3]) + statement_id = self.path.split("/")[-1].split("?")[0] + revision = self.querystring.get("RevisionId", "") if self.backend.get_function(function_name): self.backend.remove_permission(function_name, statement_id, revision) - return 204, {}, "{}" + return 204, {"status": 204}, "{}" else: - return 404, {}, "{}" + return 404, {"status": 404}, "{}" - def _invoke(self, request: Any) -> Tuple[int, Dict[str, str], Union[str, bytes]]: + @amz_crc32 + def invoke(self) -> Tuple[int, Dict[str, str], Union[str, bytes]]: response_headers: Dict[str, str] = {} # URL Decode in case it's a ARN: @@ -264,7 +68,7 @@ def _invoke(self, request: Any) -> Tuple[int, Dict[str, str], Union[str, bytes]] function_name, qualifier, self.body, self.headers, response_headers ) if payload is not None: - if request.headers.get("X-Amz-Invocation-Type") != "Event": + if self.headers.get("X-Amz-Invocation-Type") != "Event": if sys.getsizeof(payload) > 6000000: response_headers["Content-Length"] = "142" response_headers["x-amz-function-error"] = "Unhandled" @@ -275,13 +79,15 @@ def _invoke(self, request: Any) -> Tuple[int, Dict[str, str], Union[str, bytes]] payload = json.dumps(error_dict).encode("utf-8") response_headers["content-type"] = "application/json" - if request.headers.get("X-Amz-Invocation-Type") == "Event": + if self.headers.get("X-Amz-Invocation-Type") == "Event": status_code = 202 - elif request.headers.get("X-Amz-Invocation-Type") == "DryRun": + response_headers["status"] = "202" + elif self.headers.get("X-Amz-Invocation-Type") == "DryRun": status_code = 204 + response_headers["status"] = "204" else: if ( - request.headers.get("X-Amz-Log-Type") != "Tail" + self.headers.get("X-Amz-Log-Type") != "Tail" and "x-amz-log-result" in response_headers ): del response_headers["x-amz-log-result"] @@ -290,7 +96,8 @@ def _invoke(self, request: Any) -> Tuple[int, Dict[str, str], Union[str, bytes]] else: return 404, response_headers, "{}" - def _invoke_async(self) -> Tuple[int, Dict[str, str], Union[str, bytes]]: + @amz_crc32 + def invoke_async(self) -> Tuple[int, Dict[str, str], Union[str, bytes]]: response_headers: Dict[str, Any] = {} function_name = unquote(self.path.rsplit("/", 3)[-3]) @@ -298,9 +105,10 @@ def _invoke_async(self) -> Tuple[int, Dict[str, str], Union[str, bytes]]: fn = self.backend.get_function(function_name, None) payload = fn.invoke(self.body, self.headers, response_headers) response_headers["Content-Length"] = str(len(payload)) + response_headers["status"] = 202 return 202, response_headers, payload - def _list_functions(self) -> TYPE_RESPONSE: + def list_functions(self) -> str: querystring = self.querystring func_version = querystring.get("FunctionVersion", [None])[0] result: Dict[str, List[Dict[str, Any]]] = {"Functions": []} @@ -309,9 +117,10 @@ def _list_functions(self) -> TYPE_RESPONSE: json_data = fn.get_configuration() result["Functions"].append(json_data) - return 200, {}, json.dumps(result) + return json.dumps(result) - def _list_versions_by_function(self, function_name: str) -> TYPE_RESPONSE: + def list_versions_by_function(self) -> str: + function_name = self.path.split("/")[-2] result: Dict[str, Any] = {"Versions": []} functions = self.backend.list_versions_by_function(function_name) @@ -319,9 +128,11 @@ def _list_versions_by_function(self, function_name: str) -> TYPE_RESPONSE: json_data = fn.get_configuration() result["Versions"].append(json_data) - return 200, {}, json.dumps(result) + return json.dumps(result) - def _list_aliases(self, function_name: str) -> TYPE_RESPONSE: + def list_aliases(self) -> TYPE_RESPONSE: + path = self.path + function_name = path.split("/")[-2] result: Dict[str, Any] = {"Aliases": []} aliases = self.backend.list_aliases(function_name) @@ -331,49 +142,50 @@ def _list_aliases(self, function_name: str) -> TYPE_RESPONSE: return 200, {}, json.dumps(result) - def _create_function(self) -> TYPE_RESPONSE: + def create_function(self) -> TYPE_RESPONSE: function_name = self.json_body["FunctionName"].rsplit(":", 1)[-1] try: self.backend.get_function(function_name, None) except UnknownFunctionException: fn = self.backend.create_function(self.json_body) config = fn.get_configuration(on_create=True) - return 201, {}, json.dumps(config) + return 201, {"status": 201}, json.dumps(config) raise FunctionAlreadyExists(function_name) - def _create_function_url_config(self) -> TYPE_RESPONSE: + def create_function_url_config(self) -> TYPE_RESPONSE: function_name = unquote(self.path.split("/")[-2]) config = self.backend.create_function_url_config(function_name, self.json_body) - return 201, {}, json.dumps(config.to_dict()) + return 201, {"status": 201}, json.dumps(config.to_dict()) - def _delete_function_url_config(self) -> TYPE_RESPONSE: + def delete_function_url_config(self) -> TYPE_RESPONSE: function_name = unquote(self.path.split("/")[-2]) self.backend.delete_function_url_config(function_name) - return 204, {}, "{}" + return 204, {"status": 204}, "{}" - def _get_function_url_config(self) -> TYPE_RESPONSE: + def get_function_url_config(self) -> TYPE_RESPONSE: function_name = unquote(self.path.split("/")[-2]) config = self.backend.get_function_url_config(function_name) - return 201, {}, json.dumps(config.to_dict()) + return 201, {"status": 201}, json.dumps(config.to_dict()) - def _update_function_url_config(self) -> TYPE_RESPONSE: + def update_function_url_config(self) -> str: function_name = unquote(self.path.split("/")[-2]) config = self.backend.update_function_url_config(function_name, self.json_body) - return 200, {}, json.dumps(config.to_dict()) + return json.dumps(config.to_dict()) - def _create_event_source_mapping(self) -> TYPE_RESPONSE: + def create_event_source_mapping(self) -> TYPE_RESPONSE: fn = self.backend.create_event_source_mapping(self.json_body) config = fn.get_configuration() - return 201, {}, json.dumps(config) + return 201, {"status": 201}, json.dumps(config) - def _list_event_source_mappings( - self, event_source_arn: str, function_name: str - ) -> TYPE_RESPONSE: + def list_event_source_mappings(self) -> str: + event_source_arn = self.querystring.get("EventSourceArn", [None])[0] + function_name = self.querystring.get("FunctionName", [None])[0] esms = self.backend.list_event_source_mappings(event_source_arn, function_name) result = {"EventSourceMappings": [esm.get_configuration() for esm in esms]} - return 200, {}, json.dumps(result) + return json.dumps(result) - def _get_event_source_mapping(self, uuid: str) -> TYPE_RESPONSE: + def get_event_source_mapping(self) -> TYPE_RESPONSE: + uuid = self.path.split("/")[-1] result = self.backend.get_event_source_mapping(uuid) if result: return 200, {}, json.dumps(result.get_configuration()) @@ -382,39 +194,41 @@ def _get_event_source_mapping(self, uuid: str) -> TYPE_RESPONSE: "Type": "User", "Message": "The resource you requested does not exist.", } - headers = {"x-amzn-errortype": "ResourceNotFoundException"} + headers = {"x-amzn-errortype": "ResourceNotFoundException", "status": 404} return 404, headers, json.dumps(err) - def _update_event_source_mapping(self, uuid: str) -> TYPE_RESPONSE: + def update_event_source_mapping(self) -> TYPE_RESPONSE: + uuid = self.path.split("/")[-1] result = self.backend.update_event_source_mapping(uuid, self.json_body) if result: - return 202, {}, json.dumps(result.get_configuration()) + return 202, {"status": 202}, json.dumps(result.get_configuration()) else: return 404, {}, "{}" - def _delete_event_source_mapping(self, uuid: str) -> TYPE_RESPONSE: + def delete_event_source_mapping(self) -> TYPE_RESPONSE: + uuid = self.path.split("/")[-1] esm = self.backend.delete_event_source_mapping(uuid) if esm: json_result = esm.get_configuration() json_result.update({"State": "Deleting"}) - return 202, {}, json.dumps(json_result) + return 202, {"status": 202}, json.dumps(json_result) else: return 404, {}, "{}" - def _publish_function(self) -> TYPE_RESPONSE: + def publish_version(self) -> TYPE_RESPONSE: function_name = unquote(self.path.split("/")[-2]) description = self._get_param("Description") - fn = self.backend.publish_function(function_name, description) + fn = self.backend.publish_version(function_name, description) config = fn.get_configuration() # type: ignore[union-attr] - return 201, {}, json.dumps(config) + return 201, {"status": 201}, json.dumps(config) - def _delete_function(self) -> TYPE_RESPONSE: + def delete_function(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/", 1)[-1]) qualifier = self._get_param("Qualifier", None) self.backend.delete_function(function_name, qualifier) - return 204, {}, "" + return 204, {"status": 204}, "" @staticmethod def _set_configuration_qualifier( # type: ignore[misc] @@ -436,7 +250,7 @@ def _set_configuration_qualifier( # type: ignore[misc] configuration["FunctionArn"] += ":$LATEST" return configuration - def _get_function(self) -> TYPE_RESPONSE: + def get_function(self) -> str: function_name = unquote(self.path.rsplit("/", 1)[-1]) qualifier = self._get_param("Qualifier", None) @@ -446,18 +260,18 @@ def _get_function(self) -> TYPE_RESPONSE: code["Configuration"] = self._set_configuration_qualifier( code["Configuration"], function_name, qualifier ) - return 200, {}, json.dumps(code) + return json.dumps(code) - def _get_function_configuration(self) -> TYPE_RESPONSE: + def get_function_configuration(self) -> str: function_name = unquote(self.path.rsplit("/", 2)[-2]) qualifier = self._get_param("Qualifier", None) fn = self.backend.get_function(function_name, qualifier) - configuration = self._set_configuration_qualifier( + resp = self._set_configuration_qualifier( fn.get_configuration(), function_name, qualifier ) - return 200, {}, json.dumps(configuration) + return json.dumps(resp) def _get_aws_region(self, full_url: str) -> str: region = self.region_regex.search(full_url) @@ -483,9 +297,9 @@ def _untag_resource(self) -> TYPE_RESPONSE: tag_keys = self.querystring["tagKeys"] self.backend.untag_resource(function_arn, tag_keys) - return 204, {}, "{}" + return 204, {"status": 204}, "{}" - def _put_configuration(self) -> TYPE_RESPONSE: + def update_function_configuration(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/", 2)[-2]) qualifier = self._get_param("Qualifier", None) resp = self.backend.update_function_configuration( @@ -495,9 +309,9 @@ def _put_configuration(self) -> TYPE_RESPONSE: if resp: return 200, {}, json.dumps(resp) else: - return 404, {}, "{}" + return 404, {"status": 404}, "{}" - def _put_code(self) -> TYPE_RESPONSE: + def update_function_code(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/", 2)[-2]) qualifier = self._get_param("Qualifier", None) resp = self.backend.update_function_code( @@ -507,24 +321,24 @@ def _put_code(self) -> TYPE_RESPONSE: if resp: return 200, {}, json.dumps(resp) else: - return 404, {}, "{}" + return 404, {"status": 404}, "{}" - def _get_code_signing_config(self) -> TYPE_RESPONSE: + def get_function_code_signing_config(self) -> str: function_name = unquote(self.path.rsplit("/", 2)[-2]) - resp = self.backend.get_code_signing_config(function_name) - return 200, {}, json.dumps(resp) + resp = self.backend.get_function_code_signing_config(function_name) + return json.dumps(resp) - def _get_function_concurrency(self) -> TYPE_RESPONSE: + def get_function_concurrency(self) -> TYPE_RESPONSE: path_function_name = unquote(self.path.rsplit("/", 2)[-2]) function_name = self.backend.get_function(path_function_name) if function_name is None: - return 404, {}, "{}" + return 404, {"status": 404}, "{}" resp = self.backend.get_function_concurrency(path_function_name) return 200, {}, json.dumps({"ReservedConcurrentExecutions": resp}) - def _delete_function_concurrency(self) -> TYPE_RESPONSE: + def delete_function_concurrency(self) -> TYPE_RESPONSE: path_function_name = unquote(self.path.rsplit("/", 2)[-2]) function_name = self.backend.get_function(path_function_name) @@ -533,9 +347,9 @@ def _delete_function_concurrency(self) -> TYPE_RESPONSE: self.backend.delete_function_concurrency(path_function_name) - return 204, {}, "{}" + return 204, {"status": 204}, "{}" - def _put_function_concurrency(self) -> TYPE_RESPONSE: + def put_function_concurrency(self) -> TYPE_RESPONSE: path_function_name = unquote(self.path.rsplit("/", 2)[-2]) function = self.backend.get_function(path_function_name) @@ -547,40 +361,38 @@ def _put_function_concurrency(self) -> TYPE_RESPONSE: return 200, {}, json.dumps({"ReservedConcurrentExecutions": resp}) - def _list_layers(self) -> TYPE_RESPONSE: + def list_layers(self) -> str: layers = self.backend.list_layers() - return 200, {}, json.dumps({"Layers": layers}) + return json.dumps({"Layers": layers}) - def _delete_layer_version( - self, layer_name: str, layer_version: str - ) -> TYPE_RESPONSE: + def delete_layer_version(self) -> str: + layer_name = unquote(self.path.split("/")[-3]) + layer_version = self.path.split("/")[-1] self.backend.delete_layer_version(layer_name, layer_version) - return 200, {}, "{}" + return "{}" - def _get_layer_version(self, layer_name: str, layer_version: str) -> TYPE_RESPONSE: + def get_layer_version(self) -> str: + layer_name = unquote(self.path.split("/")[-3]) + layer_version = self.path.split("/")[-1] layer = self.backend.get_layer_version(layer_name, layer_version) - return 200, {}, json.dumps(layer.get_layer_version()) + return json.dumps(layer.get_layer_version()) - def _get_layer_versions(self) -> TYPE_RESPONSE: + def list_layer_versions(self) -> str: layer_name = self.path.rsplit("/", 2)[-2] - layer_versions = self.backend.get_layer_versions(layer_name) - return ( - 200, - {}, - json.dumps( - {"LayerVersions": [lv.get_layer_version() for lv in layer_versions]} - ), + layer_versions = self.backend.list_layer_versions(layer_name) + return json.dumps( + {"LayerVersions": [lv.get_layer_version() for lv in layer_versions]} ) - def _publish_layer_version(self) -> TYPE_RESPONSE: + def publish_layer_version(self) -> TYPE_RESPONSE: spec = self.json_body if "LayerName" not in spec: spec["LayerName"] = self.path.rsplit("/", 2)[-2] layer_version = self.backend.publish_layer_version(spec) config = layer_version.get_layer_version() - return 201, {}, json.dumps(config) + return 201, {"status": 201}, json.dumps(config) - def _create_alias(self) -> TYPE_RESPONSE: + def create_alias(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/", 2)[-2]) params = json.loads(self.body) alias_name = params.get("Name") @@ -594,21 +406,21 @@ def _create_alias(self) -> TYPE_RESPONSE: description=description, routing_config=routing_config, ) - return 201, {}, json.dumps(alias.to_json()) + return 201, {"status": 201}, json.dumps(alias.to_json()) - def _delete_alias(self) -> TYPE_RESPONSE: + def delete_alias(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/")[-3]) alias_name = unquote(self.path.rsplit("/", 2)[-1]) self.backend.delete_alias(name=alias_name, function_name=function_name) - return 201, {}, "{}" + return 201, {"status": 201}, "{}" - def _get_alias(self) -> TYPE_RESPONSE: + def get_alias(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/")[-3]) alias_name = unquote(self.path.rsplit("/", 2)[-1]) alias = self.backend.get_alias(name=alias_name, function_name=function_name) - return 201, {}, json.dumps(alias.to_json()) + return 201, {"status": 201}, json.dumps(alias.to_json()) - def _update_alias(self) -> TYPE_RESPONSE: + def update_alias(self) -> TYPE_RESPONSE: function_name = unquote(self.path.rsplit("/")[-3]) alias_name = unquote(self.path.rsplit("/", 2)[-1]) params = json.loads(self.body) @@ -622,40 +434,34 @@ def _update_alias(self) -> TYPE_RESPONSE: description=description, routing_config=routing_config, ) - return 201, {}, json.dumps(alias.to_json()) + return 201, {"status": 201}, json.dumps(alias.to_json()) - def event_invoke_config_handler( - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) + def put_function_event_invoke_config(self) -> str: function_name = unquote(self.path.rsplit("/", 2)[1]) + response = self.backend.put_function_event_invoke_config( + function_name, self.json_body + ) + return json.dumps(response) - if self.method == "PUT": - response = self.backend.put_function_event_invoke_config( - function_name, self.json_body - ) - return 200, {}, json.dumps(response) - elif self.method == "GET": - response = self.backend.get_function_event_invoke_config(function_name) - return 200, {}, json.dumps(response) - elif self.method == "DELETE": - self.backend.delete_function_event_invoke_config(function_name) - return 204, {}, json.dumps({}) - elif self.method == "POST": - response = self.backend.update_function_event_invoke_config( - function_name, self.json_body - ) - return 200, {}, json.dumps(response) - else: - raise NotImplementedError + def get_function_event_invoke_config(self) -> str: + function_name = unquote(self.path.rsplit("/", 2)[1]) + response = self.backend.get_function_event_invoke_config(function_name) + return json.dumps(response) - def event_invoke_config_list( - self, request: Any, full_url: str, headers: Any - ) -> TYPE_RESPONSE: - self.setup_class(request, full_url, headers) + def delete_function_event_invoke_config(self) -> TYPE_RESPONSE: + function_name = unquote(self.path.rsplit("/", 2)[1]) + self.backend.delete_function_event_invoke_config(function_name) + return 204, {"status": 204}, json.dumps({}) + + def update_function_event_invoke_config(self) -> str: + function_name = unquote(self.path.rsplit("/", 2)[1]) + response = self.backend.update_function_event_invoke_config( + function_name, self.json_body + ) + return json.dumps(response) + + def list_function_event_invoke_configs(self) -> str: function_name = unquote(self.path.rsplit("/", 3)[1]) - return ( - 200, - {}, - json.dumps(self.backend.list_function_event_invoke_configs(function_name)), + return json.dumps( + self.backend.list_function_event_invoke_configs(function_name) ) diff --git a/moto/awslambda/urls.py b/moto/awslambda/urls.py index f2699591653..9fc0c3c5ce9 100644 --- a/moto/awslambda/urls.py +++ b/moto/awslambda/urls.py @@ -4,85 +4,33 @@ url_paths = { - r"{0}/(?P[^/]+)/functions$": LambdaResponse.method_dispatch( - LambdaResponse.root - ), - r"{0}/(?P[^/]+)/functions/$": LambdaResponse.method_dispatch( - LambdaResponse.root - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/?$": LambdaResponse.method_dispatch( - LambdaResponse.function - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/aliases$": LambdaResponse.method_dispatch( - LambdaResponse.aliases - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/aliases/(?P[\w_-]+)$": LambdaResponse.method_dispatch( - LambdaResponse.alias - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/versions/?$": LambdaResponse.method_dispatch( - LambdaResponse.versions - ), - r"{0}/(?P[^/]+)/event-source-mappings/$": LambdaResponse.method_dispatch( - LambdaResponse.event_source_mappings - ), - r"{0}/(?P[^/]+)/event-source-mappings/(?P[\w_-]+)/?$": LambdaResponse.method_dispatch( - LambdaResponse.event_source_mapping - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invocations/?$": LambdaResponse.method_dispatch( - LambdaResponse.invoke - ), - r"{0}/(?P[^/]+)/functions/(?P.+)/invocations/?$": LambdaResponse.method_dispatch( - LambdaResponse.invoke - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/invoke-async$": LambdaResponse.method_dispatch( - LambdaResponse.invoke_async - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/invoke-async/$": LambdaResponse.method_dispatch( - LambdaResponse.invoke_async - ), + r"{0}/(?P[^/]+)/functions$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/aliases$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/aliases/(?P[\w_-]+)$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/versions/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/event-source-mappings/$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/event-source-mappings/(?P[\w_-]+)/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invocations/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P.+)/invocations/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/invoke-async$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/invoke-async/$": LambdaResponse.dispatch, r"{0}/(?P[^/]+)/tags/(?P.+)": LambdaResponse.method_dispatch( LambdaResponse.tag ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/policy/(?P[\w_-]+)$": LambdaResponse.method_dispatch( - LambdaResponse.policy - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/policy/?$": LambdaResponse.method_dispatch( - LambdaResponse.policy - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/configuration/?$": LambdaResponse.method_dispatch( - LambdaResponse.configuration - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/code/?$": LambdaResponse.method_dispatch( - LambdaResponse.code - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/code-signing-config$": LambdaResponse.method_dispatch( - LambdaResponse.code_signing_config - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/concurrency/?$": LambdaResponse.method_dispatch( - LambdaResponse.function_concurrency - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/url/?$": LambdaResponse.method_dispatch( - LambdaResponse.function_url_config - ), - r"{0}/(?P[^/]+)/layers$": LambdaResponse.method_dispatch( - LambdaResponse.list_layers - ), - r"{0}/(?P[^/]+)/layers/$": LambdaResponse.method_dispatch( - LambdaResponse.list_layers - ), - r"{0}/(?P[^/]+)/layers/(?P.+)/versions$": LambdaResponse.method_dispatch( - LambdaResponse.layers_versions - ), - r"{0}/(?P[^/]+)/layers/(?P.+)/versions/$": LambdaResponse.method_dispatch( - LambdaResponse.layers_versions - ), - r"{0}/(?P[^/]+)/layers/(?P.+)/versions/(?P[\w_-]+)$": LambdaResponse.method_dispatch( - LambdaResponse.layers_version - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/event-invoke-config/?$": LambdaResponse.method_dispatch( - LambdaResponse.event_invoke_config_handler - ), - r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/event-invoke-config/list$": LambdaResponse.method_dispatch( - LambdaResponse.event_invoke_config_list - ), + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/policy/(?P[\w_-]+)$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/policy/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/configuration/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/code/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/code-signing-config$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/concurrency/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/url/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/layers$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/layers/$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/layers/(?P.+)/versions$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/layers/(?P.+)/versions/$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/layers/(?P.+)/versions/(?P[\w_-]+)$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/event-invoke-config/?$": LambdaResponse.dispatch, + r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/event-invoke-config/list$": LambdaResponse.dispatch, } diff --git a/moto/core/responses.py b/moto/core/responses.py index f29801109bd..5951ab170a5 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -54,6 +54,8 @@ ResponseShape = TypeVar("ResponseShape", bound="BaseResponse") +boto3_service_name = {"awslambda": "lambda"} + def _decode_dict(d: Dict[Any, Any]) -> Dict[str, Any]: decoded: Dict[str, Any] = OrderedDict() @@ -515,9 +517,8 @@ def _get_action_from_method_and_request_uri( https://github.com/boto/botocore/blob/develop/botocore/data/iot/2015-05-28/service-2.json """ - # service response class should have 'SERVICE_NAME' class member, - # if you want to get action from method and url - conn = boto3.client(self.service_name, region_name=self.region) + service_name = boto3_service_name.get(self.service_name) or self.service_name # type: ignore + conn = boto3.client(service_name, region_name=self.region) # make cache if it does not exist yet if not hasattr(self, "method_urls"): diff --git a/tests/test_awslambda/test_lambda_eventsourcemapping.py b/tests/test_awslambda/test_lambda_eventsourcemapping.py index e5cefaf9892..ce89445d902 100644 --- a/tests/test_awslambda/test_lambda_eventsourcemapping.py +++ b/tests/test_awslambda/test_lambda_eventsourcemapping.py @@ -439,8 +439,11 @@ def test_get_event_source_mapping(): assert mapping["UUID"] == response["UUID"] assert mapping["FunctionArn"] == func["FunctionArn"] - with pytest.raises(ClientError): + with pytest.raises(ClientError) as exc: conn.get_event_source_mapping(UUID="1") + err = exc.value.response["Error"] + assert err["Code"] == "ResourceNotFoundException" + assert err["Message"] == "The resource you requested does not exist." @mock_aws @@ -516,5 +519,8 @@ def test_delete_event_source_mapping(): response = conn.delete_event_source_mapping(UUID=response["UUID"]) assert response["State"] == "Deleting" - with pytest.raises(ClientError): + with pytest.raises(ClientError) as exc: conn.get_event_source_mapping(UUID=response["UUID"]) + err = exc.value.response["Error"] + assert err["Code"] == "ResourceNotFoundException" + assert err["Message"] == "The resource you requested does not exist."