Repository: getmoto/moto
Pull request #7603 (2024-04-16 21:40:29)

Athena: Check for None on config before iterating to avoid execute_query error
Resolves: https://github.com/getmoto/moto/issues/7602

Add a None type check before iterating. Check is to a recent addition to the Athena Execution model. This allows for None configs, as is supported by Athena for configurations overridden by Workspace configs.


Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 116ce7282..ee6d58a46 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -6162,7 +6162,7 @@
 - [ ] describe_project_versions
 - [ ] describe_projects
 - [ ] describe_stream_processor
-- [ ] detect_custom_labels
+- [X] detect_custom_labels
 - [ ] detect_faces
 - [X] detect_labels
 - [ ] detect_moderation_labels
diff --git a/moto/athena/models.py b/moto/athena/models.py
index b78a0bf06..b7e8d66dc 100644
--- a/moto/athena/models.py
+++ b/moto/athena/models.py
@@ -96,7 +96,7 @@ class Execution(BaseModel):
         self.start_time = time.time()
         self.status = "SUCCEEDED"
 
-        if "OutputLocation" in self.config:
+        if self.config is not None and "OutputLocation" in self.config:
             if not self.config["OutputLocation"].endswith("/"):
                 self.config["OutputLocation"] += "/"
             self.config["OutputLocation"] += f"{self.id}.csv"
diff --git a/moto/ec2/models/amis.py b/moto/ec2/models/amis.py
index 65385a01a..4e558b6bd 100644
--- a/moto/ec2/models/amis.py
+++ b/moto/ec2/models/amis.py
@@ -145,6 +145,10 @@ class Ami(TaggedEC2Resource):
             return self.owner_id
         elif filter_name == "owner-alias":
             return self.owner_alias
+        elif filter_name == "product-code":
+            return self.product_codes
+        elif filter_name == "product-code.type":
+            return "marketplace"  # devpay is not (yet?) supported
         else:
             return super().get_filter_value(filter_name, "DescribeImages")
 
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json
index 5970424e4..fa699a34c 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-west-3.json
@@ -1695,6 +1695,50 @@
   "InstanceType": "c6in.xlarge",
   "Location": "euw3-az2"
  },
+ {
+  "InstanceType": "c7i.12xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.16xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.24xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.2xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.48xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.4xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.8xlarge",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.large",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.metal-24xl",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.metal-48xl",
+  "Location": "euw3-az2"
+ },
+ {
+  "InstanceType": "c7i.xlarge",
+  "Location": "euw3-az2"
+ },
  {
   "InstanceType": "d2.2xlarge",
   "Location": "euw3-az2"
@@ -3103,6 +3147,50 @@
   "InstanceType": "c6in.xlarge",
   "Location": "euw3-az3"
  },
+ {
+  "InstanceType": "c7i.12xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.16xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.24xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.2xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.48xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.4xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.8xlarge",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.large",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.metal-24xl",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.metal-48xl",
+  "Location": "euw3-az3"
+ },
+ {
+  "InstanceType": "c7i.xlarge",
+  "Location": "euw3-az3"
+ },
  {
   "InstanceType": "d2.2xlarge",
   "Location": "euw3-az3"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json
index 4b37de384..7e3a1787f 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-west-3.json
@@ -1695,6 +1695,50 @@
   "InstanceType": "c6in.xlarge",
   "Location": "eu-west-3b"
  },
+ {
+  "InstanceType": "c7i.12xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.16xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.24xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.2xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.48xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.4xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.8xlarge",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.large",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.metal-24xl",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.metal-48xl",
+  "Location": "eu-west-3b"
+ },
+ {
+  "InstanceType": "c7i.xlarge",
+  "Location": "eu-west-3b"
+ },
  {
   "InstanceType": "d2.2xlarge",
   "Location": "eu-west-3b"
@@ -3103,6 +3147,50 @@
   "InstanceType": "c6in.xlarge",
   "Location": "eu-west-3c"
  },
+ {
+  "InstanceType": "c7i.12xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.16xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.24xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.2xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.48xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.4xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.8xlarge",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.large",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.metal-24xl",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.metal-48xl",
+  "Location": "eu-west-3c"
+ },
+ {
+  "InstanceType": "c7i.xlarge",
+  "Location": "eu-west-3c"
+ },
  {
   "InstanceType": "d2.2xlarge",
   "Location": "eu-west-3c"
diff --git a/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json b/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json
index 743117d03..7da76fd33 100644
--- a/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json
+++ b/moto/ec2/resources/instance_type_offerings/region/eu-west-3.json
@@ -303,6 +303,50 @@
   "InstanceType": "c6in.xlarge",
   "Location": "eu-west-3"
  },
+ {
+  "InstanceType": "c7i.12xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.16xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.24xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.2xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.48xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.4xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.8xlarge",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.large",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.metal-24xl",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.metal-48xl",
+  "Location": "eu-west-3"
+ },
+ {
+  "InstanceType": "c7i.xlarge",
+  "Location": "eu-west-3"
+ },
  {
   "InstanceType": "d2.2xlarge",
   "Location": "eu-west-3"
diff --git a/moto/ec2/responses/amis.py b/moto/ec2/responses/amis.py
index 7238c32e5..9b9d06877 100644
--- a/moto/ec2/responses/amis.py
+++ b/moto/ec2/responses/amis.py
@@ -61,7 +61,7 @@ class AmisResponse(EC2BaseResponse):
 
         # only valid attributes as per
         # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_image_attribute.html
-        valid_atrributes_list = {
+        valid_attributes_list = {
             "description": "description",
             "kernel": "kernel_id",
             "ramdisk": "ramdisk",
@@ -75,7 +75,7 @@ class AmisResponse(EC2BaseResponse):
             "lastLaunchedTime": "lld",
             "imdsSupport": "imds",
         }
-        if attribute_name not in valid_atrributes_list:
+        if attribute_name not in valid_attributes_list:
             raise InvalidRequest
         elif attribute_name == "blockDeviceMapping":
             # replicate real aws behaviour and throw and error
@@ -86,11 +86,11 @@ class AmisResponse(EC2BaseResponse):
         launch_permissions = None
         if attribute_name == "launchPermission":
             launch_permissions = self.ec2_backend.describe_image_attribute(
-                ami_id, valid_atrributes_list[attribute_name]
+                ami_id, valid_attributes_list[attribute_name]
             )
         else:
             attribute_value = self.ec2_backend.describe_image_attribute(
-                ami_id, valid_atrributes_list[attribute_name]
+                ami_id, valid_attributes_list[attribute_name]
             )
 
         template = self.response_template(DESCRIBE_IMAGE_ATTRIBUTES_RESPONSE)
diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py
index 2e0f47da2..a162df8d2 100644
--- a/moto/ec2/utils.py
+++ b/moto/ec2/utils.py
@@ -431,6 +431,7 @@ filter_dict_attribute_mapping = {
     "subnet-id": "subnet_id",
     "dns-name": "public_dns",
     "key-name": "key_name",
+    "product-code": "product_codes",
 }
 
 
diff --git a/moto/ecs/models.py b/moto/ecs/models.py
index 72acea49f..adfc7b5ec 100644
--- a/moto/ecs/models.py
+++ b/moto/ecs/models.py
@@ -703,7 +703,12 @@ class Service(BaseObject, CloudFormationModel):
             )
         else:
             return ecs_backend.update_service(
-                cluster_name, service_name, task_definition, desired_count
+                {
+                    "cluster": cluster_name,
+                    "service": service_name,
+                    "task_definition": task_definition,
+                    "desired_count": desired_count,
+                }
             )
 
     @classmethod
@@ -1677,26 +1682,22 @@ class EC2ContainerServiceBackend(BaseBackend):
 
         return result, failures
 
-    def update_service(
-        self,
-        cluster_str: str,
-        service_str: str,
-        task_definition_str: str,
-        desired_count: Optional[int],
-    ) -> Service:
+    def update_service(self, service_properties: Dict[str, Any]) -> Service:
+        cluster_str = service_properties.pop("cluster", "default")
+        task_definition_str = service_properties.pop("task_definition", None)
         cluster = self._get_cluster(cluster_str)
-
-        service_name = service_str.split("/")[-1]
+        service_name = service_properties.pop("service").split("/")[-1]
         cluster_service_pair = f"{cluster.name}:{service_name}"
+
         if cluster_service_pair in self.services:
-            if task_definition_str is not None:
+            current_service = self.services[cluster_service_pair]
+            for prop_name, prop_val in service_properties.items():
+                if prop_val is not None:
+                    current_service.__setattr__(prop_name, prop_val)
+            if task_definition_str:
                 self.describe_task_definition(task_definition_str)
-                self.services[
-                    cluster_service_pair
-                ].task_definition = task_definition_str
-            if desired_count is not None:
-                self.services[cluster_service_pair].desired_count = desired_count
-            return self.services[cluster_service_pair]
+                current_service.task_definition = task_definition_str
+            return current_service
         else:
             raise ServiceNotFoundException
 
diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py
index dd40d60cf..f388ad16e 100644
--- a/moto/ecs/responses.py
+++ b/moto/ecs/responses.py
@@ -2,6 +2,7 @@ import json
 from typing import Any, Dict
 
 from moto.core.responses import BaseResponse
+from moto.core.utils import camelcase_to_underscores
 
 from .models import EC2ContainerServiceBackend, ecs_backends
 
@@ -306,13 +307,12 @@ class EC2ContainerServiceResponse(BaseResponse):
         return json.dumps(resp)
 
     def update_service(self) -> str:
-        cluster_str = self._get_param("cluster", "default")
-        service_name = self._get_param("service")
-        task_definition = self._get_param("taskDefinition")
-        desired_count = self._get_int_param("desiredCount")
-        service = self.ecs_backend.update_service(
-            cluster_str, service_name, task_definition, desired_count
-        )
+        service_properties = json.loads(self.body)
+        parsed_props = {}
+        for k, v in service_properties.items():
+            parsed_props[camelcase_to_underscores(k)] = v
+
+        service = self.ecs_backend.update_service(parsed_props)
         return json.dumps({"service": service.response_object})
 
     def delete_service(self) -> str:
diff --git a/moto/rekognition/models.py b/moto/rekognition/models.py
index d7f05d7ba..9445832ce 100644
--- a/moto/rekognition/models.py
+++ b/moto/rekognition/models.py
@@ -76,6 +76,9 @@ class RekognitionBackend(BaseBackend):
             "3.0",
         )
 
+    def detect_custom_labels(self) -> Tuple[List[Dict[str, Any]]]:
+        return (self._detect_custom_labels_detections(),)
+
     # private
 
     def _job_id(self) -> str:
@@ -755,5 +758,21 @@ class RekognitionBackend(BaseBackend):
             },
         ]
 
+    def _detect_custom_labels_detections(self) -> List[Dict[str, Any]]:
+        return [
+            {
+                "Name": "MyLogo",
+                "Confidence": 77.7729721069336,
+                "Geometry": {
+                    "BoundingBox": {
+                        "Width": 0.198987677693367,
+                        "Height": 0.31296101212501526,
+                        "Left": 0.07924537360668182,
+                        "Top": 0.4037395715713501,
+                    }
+                },
+            }
+        ]
+
 
 rekognition_backends = BackendDict(RekognitionBackend, "rekognition")
diff --git a/moto/rekognition/responses.py b/moto/rekognition/responses.py
index a18e912c7..abf56694b 100644
--- a/moto/rekognition/responses.py
+++ b/moto/rekognition/responses.py
@@ -103,6 +103,14 @@ class RekognitionResponse(BaseResponse):
             )
         )
 
+    def detect_custom_labels(self) -> str:
+        (custom_labels,) = self.rekognition_backend.detect_custom_labels()
+        return json.dumps(
+            dict(
+                CustomLabels=custom_labels,
+            )
+        )
+
     def start_face_search(self) -> TYPE_RESPONSE:
         headers = {"Content-Type": "application/x-amz-json-1.1"}
         job_id = self.rekognition_backend.start_face_search()
diff --git a/moto/sagemaker/models.py b/moto/sagemaker/models.py
index a86a9ad66..a544f3ce6 100644
--- a/moto/sagemaker/models.py
+++ b/moto/sagemaker/models.py
@@ -2176,13 +2176,31 @@ class SageMakerModelBackend(BaseBackend):
         ]
 
     def create_trial_component(
-        self, trial_component_name: str, trial_name: str
+        self,
+        trial_component_name: str,
+        trial_name: str,
+        status: Dict[str, str],
+        start_time: Optional[datetime],
+        end_time: Optional[datetime],
+        display_name: Optional[str],
+        parameters: Optional[Dict[str, Dict[str, Union[str, float]]]],
+        input_artifacts: Optional[Dict[str, Dict[str, str]]],
+        output_artifacts: Optional[Dict[str, Dict[str, str]]],
+        metadata_properties: Optional[Dict[str, str]],
     ) -> Dict[str, Any]:
         trial_component = FakeTrialComponent(
             account_id=self.account_id,
             region_name=self.region_name,
+            display_name=display_name,
+            start_time=start_time,
+            end_time=end_time,
+            parameters=parameters,
+            input_artifacts=input_artifacts,
+            output_artifacts=output_artifacts,
+            metadata_properties=metadata_properties,
             trial_component_name=trial_component_name,
             trial_name=trial_name,
+            status=status,
             tags=[],
         )
         self.trial_components[trial_component_name] = trial_component
@@ -2264,6 +2282,62 @@ class SageMakerModelBackend(BaseBackend):
             "TrialArn": f"arn:aws:sagemaker:{self.region_name}:{self.account_id}:experiment-trial/{trial_name}",
         }
 
+    def update_trial_component(
+        self,
+        trial_component_name: str,
+        status: Optional[Dict[str, str]],
+        display_name: Optional[str],
+        start_time: Optional[datetime],
+        end_time: Optional[datetime],
+        parameters: Optional[Dict[str, Dict[str, Union[str, float]]]],
+        parameters_to_remove: Optional[List[str]],
+        input_artifacts: Optional[Dict[str, Dict[str, str]]],
+        input_artifacts_to_remove: Optional[List[str]],
+        output_artifacts: Optional[Dict[str, Dict[str, str]]],
+        output_artifacts_to_remove: Optional[List[str]],
+    ) -> Dict[str, str]:
+        try:
+            trial_component = self.trial_components[trial_component_name]
+        except KeyError:
+            arn = FakeTrialComponent.arn_formatter(
+                trial_component_name, self.account_id, self.region_name
+            )
+            raise ValidationError(message=f"Could not find trial component '{arn}'")
+
+        if status:
+            trial_component.status = status
+        if display_name:
+            trial_component.display_name = display_name
+        if start_time:
+            trial_component.start_time = start_time
+        if end_time:
+            trial_component.end_time = end_time
+        if parameters:
+            trial_component.parameters = parameters
+        if input_artifacts:
+            trial_component.input_artifacts = input_artifacts
+        if output_artifacts:
+            trial_component.output_artifacts = output_artifacts
+
+        trial_component.last_modified_time = datetime.now().strftime(
+            "%Y-%m-%d %H:%M:%S"
+        )
+
+        for parameter_to_remove in parameters_to_remove or []:
+            trial_component.parameters.pop(parameter_to_remove)
+
+        for input_artifact_to_remove in input_artifacts_to_remove or []:
+            trial_component.input_artifacts.pop(input_artifact_to_remove)
+
+        for output_artifact_to_remove in output_artifacts_to_remove or []:
+            trial_component.output_artifacts.pop(output_artifact_to_remove)
+
+        return {
+            "TrialComponentArn": FakeTrialComponent.arn_formatter(
+                trial_component_name, self.account_id, self.region_name
+            )
+        }
+
     def create_notebook_instance(
         self,
         notebook_instance_name: str,
@@ -3650,17 +3724,39 @@ class FakeTrialComponent(BaseObject):
         account_id: str,
         region_name: str,
         trial_component_name: str,
+        display_name: Optional[str],
+        start_time: Optional[datetime],
+        end_time: Optional[datetime],
+        parameters: Optional[Dict[str, Dict[str, Union[str, float]]]],
+        input_artifacts: Optional[Dict[str, Dict[str, str]]],
+        output_artifacts: Optional[Dict[str, Dict[str, str]]],
+        metadata_properties: Optional[Dict[str, str]],
+        status: Optional[Dict[str, str]],
         trial_name: Optional[str],
         tags: List[Dict[str, str]],
     ):
         self.trial_component_name = trial_component_name
+        self.display_name = (
+            display_name if display_name is not None else trial_component_name
+        )
         self.trial_component_arn = FakeTrialComponent.arn_formatter(
             trial_component_name, account_id, region_name
         )
+        self.status = status
         self.tags = tags
         self.trial_name = trial_name
+        self.start_time = start_time
+        self.end_time = end_time
         now_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
         self.creation_time = self.last_modified_time = now_string
+        self.created_by: Dict[str, Union[Dict[str, str], str]] = {}
+        self.last_modified_by: Dict[str, Union[Dict[str, str], str]] = {}
+        self.parameters = parameters if parameters is not None else {}
+        self.input_artifacts = input_artifacts if input_artifacts is not None else {}
+        self.output_artifacts = output_artifacts if output_artifacts is not None else {}
+        self.metadata_properties = metadata_properties
+        self.metrics: List[Dict[str, Union[float, datetime, str]]] = []
+        self.sources: List[Dict[str, str]] = []
 
     @property
     def response_object(self) -> Dict[str, Any]:  # type: ignore[misc]
diff --git a/moto/sagemaker/responses.py b/moto/sagemaker/responses.py
index 8569a1651..979033a1f 100644
--- a/moto/sagemaker/responses.py
+++ b/moto/sagemaker/responses.py
@@ -407,6 +407,14 @@ class SageMakerResponse(BaseResponse):
     def create_trial_component(self) -> TYPE_RESPONSE:
         response = self.sagemaker_backend.create_trial_component(
             trial_component_name=self._get_param("TrialComponentName"),
+            start_time=self._get_param("StartTime"),
+            end_time=self._get_param("EndTime"),
+            display_name=self._get_param("DisplayName"),
+            parameters=self._get_param("Parameters"),
+            input_artifacts=self._get_param("InputArtifacts"),
+            output_artifacts=self._get_param("OutputArtifacts"),
+            metadata_properties=self._get_param("MetadataProperties"),
+            status=self._get_param("Status"),
             trial_name=self._get_param("TrialName"),
         )
         return 200, {}, json.dumps(response)
@@ -447,6 +455,22 @@ class SageMakerResponse(BaseResponse):
         )
         return 200, {}, json.dumps(response)
 
+    def update_trial_component(self) -> TYPE_RESPONSE:
+        response = self.sagemaker_backend.update_trial_component(
+            trial_component_name=self._get_param("TrialComponentName"),
+            status=self._get_param("Status"),
+            display_name=self._get_param("DisplayName"),
+            start_time=self._get_param("StartTime"),
+            end_time=self._get_param("EndTime"),
+            parameters=self._get_param("Parameters"),
+            parameters_to_remove=self._get_param("ParametersToRemove"),
+            input_artifacts=self._get_param("InputArtifacts"),
+            input_artifacts_to_remove=self._get_param("InputArtifactsToRemove"),
+            output_artifacts=self._get_param("OutputArtifacts"),
+            output_artifacts_to_remove=self._get_param("OutputArtifactsToRemove"),
+        )
+        return 200, {}, json.dumps(response)
+
     def describe_pipeline(self) -> TYPE_RESPONSE:
         response = self.sagemaker_backend.describe_pipeline(
             self._get_param("PipelineName")
diff --git a/moto/ssm/resources/regions.json b/moto/ssm/resources/regions.json
index 90a07ae72..87ccd9ca8 100644
--- a/moto/ssm/resources/regions.json
+++ b/moto/ssm/resources/regions.json
@@ -1243,6 +1243,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.af-south-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -1318,6 +1327,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "s3outposts": {
                 "Value": "s3outposts",
                 "endpoint": {
@@ -2894,6 +2906,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ap-east-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -2960,6 +2981,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "s3outposts": {
                 "Value": "s3outposts",
                 "endpoint": {
@@ -12690,6 +12714,24 @@
                   "Value": "HTTPS"
                 }
               },
+              "cognito-identity": {
+                "Value": "cognito-identity",
+                "endpoint": {
+                  "Value": "cognito-identity.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "cognito-idp": {
+                "Value": "cognito-idp",
+                "endpoint": {
+                  "Value": "cognito-idp.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "compute-optimizer": {
                 "Value": "compute-optimizer",
                 "endpoint": {
@@ -13323,6 +13365,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -13380,6 +13431,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "sagemaker": {
                 "Value": "sagemaker",
                 "endpoint": {
@@ -13677,6 +13731,9 @@
             "local-zones": {
               "apse1-bkk1-az1": {
                 "Value": "apse1-bkk1-az1"
+              },
+              "apse1-mnl1-az1": {
+                "Value": "apse1-mnl1-az1"
               }
             },
             "longName": {
@@ -13974,15 +14031,6 @@
               "bedrock": {
                 "Value": "bedrock"
               },
-              "budgets": {
-                "Value": "budgets",
-                "endpoint": {
-                  "Value": "budgets.amazonaws.com"
-                },
-                "protocols": {
-                  "Value": "HTTPS"
-                }
-              },
               "chatbot": {
                 "Value": "chatbot"
               },
@@ -16611,6 +16659,9 @@
                   "Value": "HTTPS"
                 }
               },
+              "bedrock": {
+                "Value": "bedrock"
+              },
               "budgets": {
                 "Value": "budgets",
                 "endpoint": {
@@ -20149,6 +20200,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "s3outposts": {
                 "Value": "s3outposts",
                 "endpoint": {
@@ -21392,6 +21446,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ap-southeast-4.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -21449,6 +21512,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "sagemaker": {
                 "Value": "sagemaker",
                 "endpoint": {
@@ -24540,6 +24606,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "kafka": {
+                "Value": "kafka",
+                "endpoint": {
+                  "Value": "kafka.ca-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "kinesis": {
                 "Value": "kinesis",
                 "endpoint": {
@@ -24690,6 +24765,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ca-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -24918,6 +25002,15 @@
               "trustedadvisor": {
                 "Value": "trustedadvisor"
               },
+              "verifiedpermissions": {
+                "Value": "verifiedpermissions",
+                "endpoint": {
+                  "Value": "verifiedpermissions.ca-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "vpc": {
                 "Value": "vpc"
               },
@@ -29048,15 +29141,6 @@
                   "Value": "HTTPS"
                 }
               },
-              "iot-roborunner": {
-                "Value": "iot-roborunner",
-                "endpoint": {
-                  "Value": "iotroborunner.eu-central-1.amazonaws.com"
-                },
-                "protocols": {
-                  "Value": "HTTPS"
-                }
-              },
               "iot1click-projects": {
                 "Value": "iot1click-projects",
                 "endpoint": {
@@ -31457,6 +31541,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.eu-central-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -31511,6 +31604,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "sagemaker": {
                 "Value": "sagemaker",
                 "endpoint": {
@@ -35026,6 +35122,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.eu-south-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -35101,6 +35206,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "s3outposts": {
                 "Value": "s3outposts",
                 "endpoint": {
@@ -35702,6 +35810,24 @@
                   "Value": "HTTPS"
                 }
               },
+              "cognito-identity": {
+                "Value": "cognito-identity",
+                "endpoint": {
+                  "Value": "cognito-identity.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "cognito-idp": {
+                "Value": "cognito-idp",
+                "endpoint": {
+                  "Value": "cognito-idp.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "compute-optimizer": {
                 "Value": "compute-optimizer",
                 "endpoint": {
@@ -36329,6 +36455,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -36386,6 +36521,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "sagemaker": {
                 "Value": "sagemaker",
                 "endpoint": {
@@ -44969,6 +45107,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.il-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -45023,6 +45170,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "s3outposts": {
                 "Value": "s3outposts",
                 "endpoint": {
@@ -45272,6 +45422,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "verifiedpermissions": {
+                "Value": "verifiedpermissions",
+                "endpoint": {
+                  "Value": "verifiedpermissions.il-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "vpc": {
                 "Value": "vpc"
               },
@@ -46368,6 +46527,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "resource-explorer-2": {
+                "Value": "resource-explorer-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.me-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "resource-groups": {
                 "Value": "resource-groups",
                 "endpoint": {
@@ -46425,6 +46593,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "sagemaker": {
                 "Value": "sagemaker",
                 "endpoint": {
@@ -48028,6 +48199,9 @@
                   "Value": "HTTPS, HTTP"
                 }
               },
+              "s3control": {
+                "Value": "s3control"
+              },
               "s3outposts": {
                 "Value": "s3outposts",
                 "endpoint": {
@@ -50430,15 +50604,30 @@
               "Value": "US-VA"
             },
             "local-zones": {
+              "use1-atl2-az1": {
+                "Value": "use1-atl2-az1"
+              },
               "use1-bos1-az1": {
                 "Value": "use1-bos1-az1"
               },
+              "use1-chi2-az1": {
+                "Value": "use1-chi2-az1"
+              },
+              "use1-dfw2-az1": {
+                "Value": "use1-dfw2-az1"
+              },
+              "use1-iah2-az1": {
+                "Value": "use1-iah2-az1"
+              },
               "use1-lim1-az1": {
                 "Value": "use1-lim1-az1"
               },
               "use1-mci1-az1": {
                 "Value": "use1-mci1-az1"
               },
+              "use1-mia2-az1": {
+                "Value": "use1-mia2-az1"
+              },
               "use1-msp1-az1": {
                 "Value": "use1-msp1-az1"
               },
@@ -51917,15 +52106,6 @@
                   "Value": "HTTPS"
                 }
               },
-              "iot-roborunner": {
-                "Value": "iot-roborunner",
-                "endpoint": {
-                  "Value": "iotroborunner.us-east-1.amazonaws.com"
-                },
-                "protocols": {
-                  "Value": "HTTPS"
-                }
-              },
               "iot1click-projects": {
                 "Value": "iot1click-projects",
                 "endpoint": {
@@ -57582,6 +57762,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "verifiedpermissions": {
+                "Value": "verifiedpermissions",
+                "endpoint": {
+                  "Value": "verifiedpermissions.us-gov-east-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "vmwarecloudonaws": {
                 "Value": "vmwarecloudonaws"
               },
@@ -59278,6 +59467,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "verifiedpermissions": {
+                "Value": "verifiedpermissions",
+                "endpoint": {
+                  "Value": "verifiedpermissions.us-gov-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "vmwarecloudonaws": {
                 "Value": "vmwarecloudonaws"
               },
diff --git a/moto/ssm/resources/services.json b/moto/ssm/resources/services.json
index f26614d84..86b4d1dd0 100644
--- a/moto/ssm/resources/services.json
+++ b/moto/ssm/resources/services.json
@@ -7845,6 +7845,9 @@
               "ap-southeast-1": {
                 "Value": "ap-southeast-1"
               },
+              "ap-southeast-2": {
+                "Value": "ap-southeast-2"
+              },
               "eu-central-1": {
                 "Value": "eu-central-1"
               },
@@ -7962,15 +7965,6 @@
                   "Value": "HTTPS"
                 }
               },
-              "ap-southeast-1": {
-                "Value": "ap-southeast-1",
-                "endpoint": {
-                  "Value": "budgets.amazonaws.com"
-                },
-                "protocols": {
-                  "Value": "HTTPS"
-                }
-              },
               "ap-southeast-2": {
                 "Value": "ap-southeast-2",
                 "endpoint": {
@@ -13767,6 +13761,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-south-2": {
+                "Value": "ap-south-2",
+                "endpoint": {
+                  "Value": "cognito-identity.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ap-southeast-1": {
                 "Value": "ap-southeast-1",
                 "endpoint": {
@@ -13857,6 +13860,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-south-2": {
+                "Value": "eu-south-2",
+                "endpoint": {
+                  "Value": "cognito-identity.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-west-1": {
                 "Value": "eu-west-1",
                 "endpoint": {
@@ -14018,6 +14030,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-south-2": {
+                "Value": "ap-south-2",
+                "endpoint": {
+                  "Value": "cognito-idp.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ap-southeast-1": {
                 "Value": "ap-southeast-1",
                 "endpoint": {
@@ -14099,6 +14120,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-south-2": {
+                "Value": "eu-south-2",
+                "endpoint": {
+                  "Value": "cognito-idp.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-west-1": {
                 "Value": "eu-west-1",
                 "endpoint": {
@@ -16849,6 +16879,9 @@
             "longName": {
               "Value": "Amazon DynamoDB Accelerator"
             },
+            "marketingHomeURL": {
+              "Value": "https://aws.amazon.com/dynamodb/dax/"
+            },
             "regions": {
               "ap-northeast-1": {
                 "Value": "ap-northeast-1",
@@ -32606,7 +32639,6 @@
             }
           },
           "iot-roborunner": {
-            "Value": "iot-roborunner",
             "longName": {
               "Value": "AWS IoT RoboRunner"
             },
@@ -34498,6 +34530,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ca-west-1": {
+                "Value": "ca-west-1",
+                "endpoint": {
+                  "Value": "kafka.ca-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "cn-north-1": {
                 "Value": "cn-north-1",
                 "endpoint": {
@@ -50504,6 +50545,24 @@
               "Value": "https://aws.amazon.com/resourceexplorer/"
             },
             "regions": {
+              "af-south-1": {
+                "Value": "af-south-1",
+                "endpoint": {
+                  "Value": "resource-explorer-2.af-south-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "ap-east-1": {
+                "Value": "ap-east-1",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ap-east-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ap-northeast-1": {
                 "Value": "ap-northeast-1",
                 "endpoint": {
@@ -50540,6 +50599,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-south-2": {
+                "Value": "ap-south-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ap-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ap-southeast-1": {
                 "Value": "ap-southeast-1",
                 "endpoint": {
@@ -50567,6 +50635,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ap-southeast-4": {
+                "Value": "ap-southeast-4",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ap-southeast-4.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "ca-central-1": {
                 "Value": "ca-central-1",
                 "endpoint": {
@@ -50576,6 +50653,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ca-west-1": {
+                "Value": "ca-west-1",
+                "endpoint": {
+                  "Value": "resource-explorer-2.ca-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-central-1": {
                 "Value": "eu-central-1",
                 "endpoint": {
@@ -50585,6 +50671,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-central-2": {
+                "Value": "eu-central-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.eu-central-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-north-1": {
                 "Value": "eu-north-1",
                 "endpoint": {
@@ -50594,6 +50689,24 @@
                   "Value": "HTTPS"
                 }
               },
+              "eu-south-1": {
+                "Value": "eu-south-1",
+                "endpoint": {
+                  "Value": "resource-explorer-2.eu-south-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "eu-south-2": {
+                "Value": "eu-south-2",
+                "endpoint": {
+                  "Value": "resource-explorer-2.eu-south-2.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-west-1": {
                 "Value": "eu-west-1",
                 "endpoint": {
@@ -50621,6 +50734,24 @@
                   "Value": "HTTPS"
                 }
               },
+              "il-central-1": {
+                "Value": "il-central-1",
+                "endpoint": {
+                  "Value": "resource-explorer-2.il-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "me-central-1": {
+                "Value": "me-central-1",
+                "endpoint": {
+                  "Value": "resource-explorer-2.me-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "me-south-1": {
                 "Value": "me-south-1",
                 "endpoint": {
@@ -53115,6 +53246,12 @@
               "Value": "AWS S3 Control"
             },
             "regions": {
+              "af-south-1": {
+                "Value": "af-south-1"
+              },
+              "ap-east-1": {
+                "Value": "ap-east-1"
+              },
               "ap-northeast-1": {
                 "Value": "ap-northeast-1"
               },
@@ -53127,12 +53264,21 @@
               "ap-south-1": {
                 "Value": "ap-south-1"
               },
+              "ap-south-2": {
+                "Value": "ap-south-2"
+              },
               "ap-southeast-1": {
                 "Value": "ap-southeast-1"
               },
               "ap-southeast-2": {
                 "Value": "ap-southeast-2"
               },
+              "ap-southeast-3": {
+                "Value": "ap-southeast-3"
+              },
+              "ap-southeast-4": {
+                "Value": "ap-southeast-4"
+              },
               "ca-central-1": {
                 "Value": "ca-central-1"
               },
@@ -53145,9 +53291,18 @@
               "eu-central-1": {
                 "Value": "eu-central-1"
               },
+              "eu-central-2": {
+                "Value": "eu-central-2"
+              },
               "eu-north-1": {
                 "Value": "eu-north-1"
               },
+              "eu-south-1": {
+                "Value": "eu-south-1"
+              },
+              "eu-south-2": {
+                "Value": "eu-south-2"
+              },
               "eu-west-1": {
                 "Value": "eu-west-1"
               },
@@ -53157,6 +53312,15 @@
               "eu-west-3": {
                 "Value": "eu-west-3"
               },
+              "il-central-1": {
+                "Value": "il-central-1"
+              },
+              "me-central-1": {
+                "Value": "me-central-1"
+              },
+              "me-south-1": {
+                "Value": "me-south-1"
+              },
               "sa-east-1": {
                 "Value": "sa-east-1"
               },
@@ -64521,6 +64685,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "ca-west-1": {
+                "Value": "ca-west-1",
+                "endpoint": {
+                  "Value": "verifiedpermissions.ca-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "eu-central-1": {
                 "Value": "eu-central-1",
                 "endpoint": {
@@ -64593,6 +64766,15 @@
                   "Value": "HTTPS"
                 }
               },
+              "il-central-1": {
+                "Value": "il-central-1",
+                "endpoint": {
+                  "Value": "verifiedpermissions.il-central-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "me-central-1": {
                 "Value": "me-central-1",
                 "endpoint": {
@@ -64638,6 +64820,24 @@
                   "Value": "HTTPS"
                 }
               },
+              "us-gov-east-1": {
+                "Value": "us-gov-east-1",
+                "endpoint": {
+                  "Value": "verifiedpermissions.us-gov-east-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
+              "us-gov-west-1": {
+                "Value": "us-gov-west-1",
+                "endpoint": {
+                  "Value": "verifiedpermissions.us-gov-west-1.amazonaws.com"
+                },
+                "protocols": {
+                  "Value": "HTTPS"
+                }
+              },
               "us-west-1": {
                 "Value": "us-west-1",
                 "endpoint": {
diff --git a/moto/stepfunctions/models.py b/moto/stepfunctions/models.py
index ab20597db..d66100532 100644
--- a/moto/stepfunctions/models.py
+++ b/moto/stepfunctions/models.py
@@ -638,7 +638,7 @@ class StepFunctionBackend(BaseBackend):
         except StateMachineDoesNotExist:
             raise ResourceNotFound(resource_arn)
 
-    def send_task_failure(self, task_token: str) -> None:
+    def send_task_failure(self, task_token: str, error: Optional[str] = None) -> None:
         pass
 
     def send_task_heartbeat(self, task_token: str) -> None:
diff --git a/moto/stepfunctions/parser/asl/component/common/error_name/custom_error_name.py b/moto/stepfunctions/parser/asl/component/common/error_name/custom_error_name.py
index bb30b8f7e..252f6e5e5 100644
--- a/moto/stepfunctions/parser/asl/component/common/error_name/custom_error_name.py
+++ b/moto/stepfunctions/parser/asl/component/common/error_name/custom_error_name.py
@@ -13,7 +13,7 @@ class CustomErrorName(ErrorName):
     _ILLEGAL_PREFIX: Final[str] = "States."
 
     def __init__(self, error_name: str):
-        if error_name.startswith(CustomErrorName._ILLEGAL_PREFIX):
+        if error_name and error_name.startswith(CustomErrorName._ILLEGAL_PREFIX):
             raise ValueError(
                 f"Custom Error Names MUST NOT begin with the prefix 'States.', got '{error_name}'."
             )
diff --git a/moto/stepfunctions/parser/asl/component/state/exec/state_task/service/state_task_service_callback.py b/moto/stepfunctions/parser/asl/component/state/exec/state_task/service/state_task_service_callback.py
index f1eb7af9b..7772bd581 100644
--- a/moto/stepfunctions/parser/asl/component/state/exec/state_task/service/state_task_service_callback.py
+++ b/moto/stepfunctions/parser/asl/component/state/exec/state_task/service/state_task_service_callback.py
@@ -1,5 +1,6 @@
 import abc
 import json
+import time
 
 from moto.stepfunctions.parser.api import (
     HistoryEventExecutionDataDetails,
@@ -49,7 +50,20 @@ class StateTaskServiceCallback(StateTaskService, abc.ABC):
         # Discard the state evaluation output.
         env.stack.pop()
 
-        callback_id = env.context_object_manager.context_object["Task"]["Token"]
+        callback_id = env.context_object_manager.context_object.get("Task", {}).get(
+            "Token"
+        )
+        if callback_id is None:
+            # We never generated a TaskToken, so there's no point in waiting until we receive it
+            # AWS seems to just wait forever  in this scenario- so let's do the same
+            timeout_seconds = self.timeout.timeout_seconds
+            mustend_at = time.time() + timeout_seconds
+            while time.time() < mustend_at:
+                if not env.is_running():
+                    return
+                time.sleep(1)
+            return
+
         callback_endpoint = env.callback_pool_manager.get(callback_id)
 
         # With Timeouts-only definition:
diff --git a/moto/stepfunctions/parser/models.py b/moto/stepfunctions/parser/models.py
index 48a95b68b..8e8bb03f3 100644
--- a/moto/stepfunctions/parser/models.py
+++ b/moto/stepfunctions/parser/models.py
@@ -106,7 +106,7 @@ class StepFunctionsParserBackend(StepFunctionBackend):
         raise InvalidToken()
 
     def send_task_success(
-        self, task_token: TaskToken, outcome: SensitiveData
+        self, task_token: TaskToken, outcome: str
     ) -> SendTaskSuccessOutput:
         outcome = CallbackOutcomeSuccess(callback_id=task_token, output=outcome)
         running_executions = self._get_executions(ExecutionStatus.RUNNING)
diff --git a/moto/stepfunctions/responses.py b/moto/stepfunctions/responses.py
index 4ad3aa1be..70633c164 100644
--- a/moto/stepfunctions/responses.py
+++ b/moto/stepfunctions/responses.py
@@ -192,7 +192,8 @@ class StepFunctionResponse(BaseResponse):
 
     def send_task_failure(self) -> TYPE_RESPONSE:
         task_token = self._get_param("taskToken")
-        self.stepfunction_backend.send_task_failure(task_token)
+        error = self._get_param("error")
+        self.stepfunction_backend.send_task_failure(task_token, error=error)
         return 200, {}, "{}"
 
     def send_task_heartbeat(self) -> TYPE_RESPONSE:
@@ -202,8 +203,8 @@ class StepFunctionResponse(BaseResponse):
 
     def send_task_success(self) -> TYPE_RESPONSE:
         task_token = self._get_param("taskToken")
-        outcome = self._get_param("outcome")
-        self.stepfunction_backend.send_task_success(task_token, outcome)
+        output = self._get_param("output")
+        self.stepfunction_backend.send_task_success(task_token, output)
         return 200, {}, "{}"
 
     def list_map_runs(self) -> TYPE_RESPONSE:
diff --git a/scripts/ci_wait_for_server.py b/scripts/ci_wait_for_server.py
index 471afbda6..c5b229e7e 100755
--- a/scripts/ci_wait_for_server.py
+++ b/scripts/ci_wait_for_server.py
@@ -14,7 +14,6 @@ expected_host = "http://localhost:{}/".format(expected_port)
 print("Waiting for service to come up on {}".format(expected_host))
 while True:
     try:
-
         urllib.urlopen(expected_host, timeout=1)
         break
     except EXCEPTIONS:
diff --git a/scripts/cloudformation_coverage.py b/scripts/cloudformation_coverage.py
index 89a4ae005..b9ae0371c 100755
--- a/scripts/cloudformation_coverage.py
+++ b/scripts/cloudformation_coverage.py
@@ -121,7 +121,9 @@ def write_main_document(supported):
         file.write("## Supported CloudFormation resources")
         file.write("\n\n")
         file.write("A list of all resources that can be created via CloudFormation. \n")
-        file.write("Please let us know if you'd like support for a resource not yet listed here.")
+        file.write(
+            "Please let us know if you'd like support for a resource not yet listed here."
+        )
         file.write("\n\n")
 
         for checklist in supported:
@@ -145,19 +147,30 @@ def write_documentation(supported):
         file.write("==================================\n")
         file.write("\n\n")
         file.write("A list of all resources that can be created via CloudFormation. \n")
-        file.write("Please let us know if you'd like support for a resource not yet listed here.")
+        file.write(
+            "Please let us know if you'd like support for a resource not yet listed here."
+        )
         file.write("\n\n")
 
         max_resource_name_length = max([len(cf.resource_name) for cf in supported]) + 2
         max_fn_att_length = 40
 
         file.write(".. table:: \n\n")
-        file.write(f"  +{('-'*max_resource_name_length)}+--------+--------+--------+{('-' * max_fn_att_length)}+\n")
-        file.write(f"  |{(' '*max_resource_name_length)}| Create | Update | Delete | {('Fn::GetAtt'.ljust(max_fn_att_length-2))} |\n")
-        file.write(f"  +{('='*max_resource_name_length)}+========+========+========+{('=' * max_fn_att_length)}+\n")
+        file.write(
+            f"  +{('-'*max_resource_name_length)}+--------+--------+--------+{('-' * max_fn_att_length)}+\n"
+        )
+        file.write(
+            f"  |{(' '*max_resource_name_length)}| Create | Update | Delete | {('Fn::GetAtt'.ljust(max_fn_att_length-2))} |\n"
+        )
+        file.write(
+            f"  +{('='*max_resource_name_length)}+========+========+========+{('=' * max_fn_att_length)}+\n"
+        )
 
         for checklist in supported:
-            attrs = [f" - [{check(att not in checklist.missing_attrs)}] {att}" for att in checklist.expected_attrs]
+            attrs = [
+                f" - [{check(att not in checklist.missing_attrs)}] {att}"
+                for att in checklist.expected_attrs
+            ]
             first_attr = attrs[0] if attrs else ""
             file.write("  |")
             file.write(checklist.resource_name.ljust(max_resource_name_length))
@@ -172,13 +185,19 @@ def write_documentation(supported):
             for index, attr in enumerate(attrs[1:]):
                 if index % 2 == 0:
                     file.write(
-                        f"  +{('-' * max_resource_name_length)}+--------+--------+--------+{attr.ljust(max_fn_att_length)}|\n")
+                        f"  +{('-' * max_resource_name_length)}+--------+--------+--------+{attr.ljust(max_fn_att_length)}|\n"
+                    )
                 else:
                     file.write(
-                        f"  |{(' ' * max_resource_name_length)}|        |        |        |{attr.ljust(max_fn_att_length)}|\n")
+                        f"  |{(' ' * max_resource_name_length)}|        |        |        |{attr.ljust(max_fn_att_length)}|\n"
+                    )
             if len(attrs) > 1 and len(attrs) % 2 == 0:
-                file.write(f"  |{(' ' * max_resource_name_length)}|        |        |        |{(' ' * max_fn_att_length)}|\n")
-            file.write(f"  +{('-'*max_resource_name_length)}+--------+--------+--------+{('-' * max_fn_att_length)}+\n")
+                file.write(
+                    f"  |{(' ' * max_resource_name_length)}|        |        |        |{(' ' * max_fn_att_length)}|\n"
+                )
+            file.write(
+                f"  +{('-'*max_resource_name_length)}+--------+--------+--------+{('-' * max_fn_att_length)}+\n"
+            )
 
 
 if __name__ == "__main__":
@@ -189,6 +208,10 @@ if __name__ == "__main__":
     # Only collect checklists for models that implement CloudFormationModel;
     # otherwise the checklist is very long and mostly empty because there
     # are so many niche AWS services and resources that moto doesn't implement yet.
-    supported = [CloudFormationChecklist(resource_name, schema) for resource_name, schema in sorted(cfn_spec["ResourceTypes"].items()) if CloudFormationChecklist(resource_name, schema).moto_model]
-    #write_main_document(supported)
+    supported = [
+        CloudFormationChecklist(resource_name, schema)
+        for resource_name, schema in sorted(cfn_spec["ResourceTypes"].items())
+        if CloudFormationChecklist(resource_name, schema).moto_model
+    ]
+    # write_main_document(supported)
     write_documentation(supported)
diff --git a/scripts/ec2_get_instance_type_offerings.py b/scripts/ec2_get_instance_type_offerings.py
index 26af2828d..3a173b2f8 100755
--- a/scripts/ec2_get_instance_type_offerings.py
+++ b/scripts/ec2_get_instance_type_offerings.py
@@ -54,8 +54,12 @@ def main():
                     instances.extend(offerings["InstanceTypeOfferings"])
                     next_token = offerings.get("NextToken", None)
                 for i in instances:
-                    del i["LocationType"]  # This can be reproduced, no need to persist it
-                instances = sorted(instances, key=lambda i: (i['Location'], i["InstanceType"]))
+                    del i[
+                        "LocationType"
+                    ]  # This can be reproduced, no need to persist it
+                instances = sorted(
+                    instances, key=lambda i: (i["Location"], i["InstanceType"])
+                )
 
                 # Ensure we use the correct US-west availability zones
                 # There are only two - for some accounts they are called us-west-1b and us-west-1c
diff --git a/scripts/pull_down_aws_managed_rules.py b/scripts/pull_down_aws_managed_rules.py
index 3f92014d6..b37a8f2f7 100755
--- a/scripts/pull_down_aws_managed_rules.py
+++ b/scripts/pull_down_aws_managed_rules.py
@@ -41,6 +41,7 @@ Summary:
         ]
     }
 """
+
 import argparse
 
 import json
diff --git a/scripts/rds_get_orderable_db_cluster_options.py b/scripts/rds_get_orderable_db_cluster_options.py
index dd63e7bf7..17749feee 100755
--- a/scripts/rds_get_orderable_db_cluster_options.py
+++ b/scripts/rds_get_orderable_db_cluster_options.py
@@ -23,9 +23,7 @@ def main():
     rds = boto3.client("rds", region_name="us-east-1")
     for engine in engines:
         print(f"Engine {engine}...")
-        dest = os.path.join(
-            root_dir, "{0}/{1}.json".format(PATH, engine)
-        )
+        dest = os.path.join(root_dir, "{0}/{1}.json".format(PATH, engine))
         try:
             options = []
             response = rds.describe_orderable_db_instance_options(Engine=engine)
diff --git a/scripts/scaffold.py b/scripts/scaffold.py
index 1f1edabf6..f0f2b43dd 100755
--- a/scripts/scaffold.py
+++ b/scripts/scaffold.py
@@ -19,6 +19,7 @@ TODO:
   - Some services's operations might cause this script to crash. If that
     should happen, please create an issue for the problem.
 """
+
 import os
 import random
 import re
@@ -41,14 +42,24 @@ from moto.core.base_backend import BaseBackend
 from inflection import singularize
 from implementation_coverage import get_moto_implementation
 
-PRIMITIVE_SHAPES = ["string", "timestamp", "integer", "boolean", "sensitiveStringType", "long"]
+PRIMITIVE_SHAPES = [
+    "string",
+    "timestamp",
+    "integer",
+    "boolean",
+    "sensitiveStringType",
+    "long",
+]
 
 TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "./template")
 
 INPUT_IGNORED_IN_BACKEND = ["Marker", "PageSize"]
 OUTPUT_IGNORED_IN_BACKEND = ["NextMarker"]
 
-root_dir = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode().strip()
+root_dir = (
+    subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode().strip()
+)
+
 
 def print_progress(title, body, color):
     """Prints a color-code message describing current state of progress."""
@@ -217,9 +228,7 @@ def get_operation_name_in_keys(operation_name, operation_keys):
     return operation_keys[index]
 
 
-def get_function_in_responses(
-    service, operation, protocol
-):  # pylint: disable=too-many-locals
+def get_function_in_responses(service, operation, protocol):  # pylint: disable=too-many-locals
     """refers to definition of API in botocore, and autogenerates function
     You can see example of elbv2 from link below.
       https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json
@@ -255,7 +264,7 @@ def get_function_in_responses(
     body += "    params = self._get_params()\n"
 
     for input_name, input_type in inputs.items():
-        body += f"    {to_snake_case(input_name)} = params.get(\"{input_name}\")\n"
+        body += f'    {to_snake_case(input_name)} = params.get("{input_name}")\n'
     if output_names:
         body += f"    {', '.join(output_names)} = self.{escaped_service}_backend.{operation}(\n"
     else:
@@ -319,10 +328,10 @@ def get_func_in_tests(service, operation):
     body = "\n\n"
     body += f"@mock_aws\n"
     body += f"def test_{operation}():\n"
-    body += f"    client = boto3.client(\"{service}\", region_name=\"{random_region}\")\n"
+    body += f'    client = boto3.client("{service}", region_name="{random_region}")\n'
     body += f"    resp = client.{operation}()\n"
     body += f"\n"
-    body += f"    raise Exception(\"NotYetImplemented\")"
+    body += f'    raise Exception("NotYetImplemented")'
     body += "\n"
     return body
 
@@ -431,7 +440,7 @@ def get_response_query_template(service, operation):  # pylint: disable=too-many
 
 def get_response_restxml_template(service, operation):
     """refers to definition of API in botocore, and autogenerates template
-        Assume that protocol is rest-xml. Shares some familiarity with protocol=query
+    Assume that protocol is rest-xml. Shares some familiarity with protocol=query
     """
     client = boto3.client(service)
     aws_operation_name = get_operation_name_in_keys(
@@ -477,11 +486,15 @@ def get_response_restxml_template(service, operation):
                 child = details.get("locationName", child)
                 if details["shape"] in PRIMITIVE_SHAPES:
                     t_member = etree.Element(child)
-                    t_member.text = "{{ " + ".".join(name_prefix) + f".{to_snake_case(child)} }}}}"
+                    t_member.text = (
+                        "{{ " + ".".join(name_prefix) + f".{to_snake_case(child)} }}}}"
+                    )
                     tree.append(t_member)
                 else:
                     t = etree.Element(child)
-                    _find_member(t, details["shape"], name_prefix + [to_snake_case(child)])
+                    _find_member(
+                        t, details["shape"], name_prefix + [to_snake_case(child)]
+                    )
                     tree.append(t)
 
     # build result
@@ -497,13 +510,23 @@ def get_response_restxml_template(service, operation):
             _find_member(t_result, name, name_prefix=["root"])
     t_root.append(t_result)
     xml_body = etree.tostring(t_root, pretty_print=True).decode("utf-8")
+
     #
     # Still need to add FOR-loops in this template
     #     <REPLACE_FOR for="x" in="y">
     # becomes
     #     {% for x in y %}
     def conv(m):
-        return m.group().replace("REPLACE_FOR", "").replace("=", "").replace('"', " ").replace("<", "{%").replace(">", "%}").strip()
+        return (
+            m.group()
+            .replace("REPLACE_FOR", "")
+            .replace("=", "")
+            .replace('"', " ")
+            .replace("<", "{%")
+            .replace(">", "%}")
+            .strip()
+        )
+
     xml_body = re.sub(r"<REPLACE_FOR[\sa-zA-Z\"=_.]+>", conv, xml_body)
     xml_body = xml_body.replace("</REPLACE_FOR>", "{% endfor %}")
     body = f'\n{operation.upper()}_TEMPLATE = """{xml_body}"""'
@@ -616,7 +639,7 @@ def insert_codes(service, operation, api_protocol):
     insert_url(service, operation, api_protocol)
 
     # Edit tests
-    tests_path= f"tests/test_{escaped_service}/test_{escaped_service}.py"
+    tests_path = f"tests/test_{escaped_service}/test_{escaped_service}.py"
     print_progress("inserting code", tests_path, "green")
     with open(tests_path, "a", encoding="utf-8") as fhandle:
         fhandle.write(func_in_tests)
@@ -624,12 +647,13 @@ def insert_codes(service, operation, api_protocol):
 
 @click.command()
 def main():
-
     click.echo("This script uses the click-module.\n")
-    click.echo(" - Start typing the name of the service you want to extend\n"
-               " - Use Tab to auto-complete the first suggest service\n"
-               " - Use the up and down-arrows on the keyboard to select something from the dropdown\n"
-               " - Press enter to continue\n")
+    click.echo(
+        " - Start typing the name of the service you want to extend\n"
+        " - Use Tab to auto-complete the first suggest service\n"
+        " - Use the up and down-arrows on the keyboard to select something from the dropdown\n"
+        " - Press enter to continue\n"
+    )
 
     """Create basic files needed for the user's choice of service and op."""
     service = select_service()
@@ -652,14 +676,16 @@ def main():
             )
 
         click.echo("Updating backend index...")
-        subprocess.check_output([f"{root_dir}/scripts/update_backend_index.py"]).decode().strip()
+        subprocess.check_output(
+            [f"{root_dir}/scripts/update_backend_index.py"]
+        ).decode().strip()
 
         click.echo(
             "\n"
             "Please select another operation, or Ctrl-X/Ctrl-C to cancel."
             "\n\n"
             "Remaining steps after development is complete:\n"
-            '- Run scripts/implementation_coverage.py,\n'
+            "- Run scripts/implementation_coverage.py,\n"
             "\n"
         )
 
diff --git a/scripts/ssm_get_default_amis.py b/scripts/ssm_get_default_amis.py
index ccd1bea5f..6c0e60ba3 100644
--- a/scripts/ssm_get_default_amis.py
+++ b/scripts/ssm_get_default_amis.py
@@ -15,7 +15,9 @@ def retrieve_by_path(client, path):
     parameters = x["Parameters"]
     next_token = x["NextToken"]
     while next_token:
-        x = client.get_parameters_by_path(Path=path, Recursive=True, NextToken=next_token)
+        x = client.get_parameters_by_path(
+            Path=path, Recursive=True, NextToken=next_token
+        )
         parameters.extend(x["Parameters"])
         next_token = x.get("NextToken")
 
@@ -32,7 +34,7 @@ def main():
     regions = boto3.Session().get_available_regions("ssm")
 
     for region in regions:
-        client = boto3.client('ssm', region_name=region)
+        client = boto3.client("ssm", region_name=region)
         ec2 = boto3.client("ec2", region_name=region)
 
         default_param_path = "/aws/service/ami-amazon-linux-latest"
@@ -41,14 +43,20 @@ def main():
         try:
             params = retrieve_by_path(client, default_param_path)
             for param in params:
-                param["LastModifiedDate"] = unix_time(param["LastModifiedDate"].astimezone(timezone.utc).replace(tzinfo=None))
+                param["LastModifiedDate"] = unix_time(
+                    param["LastModifiedDate"]
+                    .astimezone(timezone.utc)
+                    .replace(tzinfo=None)
+                )
 
             root_dir = (
                 subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
-                    .decode()
-                    .strip()
+                .decode()
+                .strip()
+            )
+            dest = os.path.join(
+                root_dir, f"moto/ssm/resources/ami-amazon-linux-latest/{region}.json"
             )
-            dest = os.path.join(root_dir, f"moto/ssm/resources/ami-amazon-linux-latest/{region}.json")
             print("Writing data to {0}".format(dest))
             with open(dest, "w") as open_file:
                 json.dump(params, open_file, sort_keys=True, indent=2)
@@ -57,7 +65,9 @@ def main():
             image_ids = [p["Value"] for p in params]
             images = ec2.describe_images(ImageIds=image_ids)["Images"]
             image_as_dicts = gen_moto_amis(images)
-            dest = os.path.join(root_dir, f"moto/ec2/resources/latest_amis/{region}.json")
+            dest = os.path.join(
+                root_dir, f"moto/ec2/resources/latest_amis/{region}.json"
+            )
             print("Writing data to {0}".format(dest))
             with open(dest, "w") as open_file:
                 json.dump(image_as_dicts, open_file, sort_keys=True, indent=2)
diff --git a/scripts/ssm_get_default_params.py b/scripts/ssm_get_default_params.py
index b995bece4..a3e80a5b2 100755
--- a/scripts/ssm_get_default_params.py
+++ b/scripts/ssm_get_default_params.py
@@ -8,13 +8,17 @@ from moto.ssm.utils import convert_to_tree
 
 
 def retrieve_by_path(client, path):
-    print(f"Retrieving all parameters from {path}. "
-          f"AWS has around 14000 parameters, and we can only retrieve 10 at the time, so this may take a while.\n\n")
+    print(
+        f"Retrieving all parameters from {path}. "
+        f"AWS has around 14000 parameters, and we can only retrieve 10 at the time, so this may take a while.\n\n"
+    )
     x = client.get_parameters_by_path(Path=path, Recursive=True)
     parameters = x["Parameters"]
     next_token = x["NextToken"]
     while next_token:
-        x = client.get_parameters_by_path(Path=path, Recursive=True, NextToken=next_token)
+        x = client.get_parameters_by_path(
+            Path=path, Recursive=True, NextToken=next_token
+        )
         parameters.extend(x["Parameters"])
         next_token = x.get("NextToken")
         if len(parameters) % 100 == 0:
@@ -36,18 +40,20 @@ def main():
          So running this scripts takes a while.
     """
 
-    client = boto3.client('ssm', region_name="us-west-1")
+    client = boto3.client("ssm", region_name="us-west-1")
 
-    default_param_paths = ["/aws/service/global-infrastructure/regions",
-                           "/aws/service/global-infrastructure/services"]
+    default_param_paths = [
+        "/aws/service/global-infrastructure/regions",
+        "/aws/service/global-infrastructure/services",
+    ]
 
     for path in default_param_paths:
         params = retrieve_by_path(client, path)
         tree = convert_to_tree(params)
         root_dir = (
             subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
-                .decode()
-                .strip()
+            .decode()
+            .strip()
         )
         filename = "{}.json".format(path.split("/")[-1])
         dest = os.path.join(root_dir, "moto/ssm/resources/{}".format(filename))
diff --git a/scripts/ssm_get_optimized_amis.py b/scripts/ssm_get_optimized_amis.py
index ce7fc1ece..97d0a6524 100644
--- a/scripts/ssm_get_optimized_amis.py
+++ b/scripts/ssm_get_optimized_amis.py
@@ -19,12 +19,15 @@ def retrieve_by_path(client, path):
     parameters = response["Parameters"]
     next_token = response["NextToken"]
     while next_token:
-        response = client.get_parameters_by_path(Path=path, Recursive=True, NextToken=next_token)
+        response = client.get_parameters_by_path(
+            Path=path, Recursive=True, NextToken=next_token
+        )
         parameters.extend(response["Parameters"])
         next_token = response.get("NextToken")
 
     return parameters
 
+
 def save_to_file(destination_path: str, params: dict):
     print("Attempting to save data to {}", destination_path)
     file_path = Path(Path.cwd(), destination_path)
@@ -49,13 +52,13 @@ def main():
      - Store this in the dedicated moto/ssm/resources-folder
     """
     for region in session.get_available_regions("ssm"):
-        ssm_client = session.client('ssm', region_name=region)
+        ssm_client = session.client("ssm", region_name=region)
 
         default_param_path = "/aws/service/ecs/optimized-ami"
 
         # Retrieve default AMI values
         try:
-            print("Retrieving data for {}" , region)
+            print("Retrieving data for {}", region)
 
             parameters = retrieve_by_path(ssm_client, default_param_path)
 
@@ -65,7 +68,11 @@ def main():
             image_ids = []
 
             for param in parameters:
-                param["LastModifiedDate"] = unix_time(param["LastModifiedDate"].astimezone(timezone.utc).replace(tzinfo=None))
+                param["LastModifiedDate"] = unix_time(
+                    param["LastModifiedDate"]
+                    .astimezone(timezone.utc)
+                    .replace(tzinfo=None)
+                )
 
                 if isinstance(param["Value"], str) and param["Value"][0] == "{":
                     param["Value"] = json.loads(param["Value"])
diff --git a/scripts/update_ssl_policies.py b/scripts/update_ssl_policies.py
index fcf447325..17b2f20cc 100755
--- a/scripts/update_ssl_policies.py
+++ b/scripts/update_ssl_policies.py
@@ -8,6 +8,7 @@ CAMEL_CASE_PATTERN = re.compile(r"(?<!^)(?=[A-Z])")
 
 KEY_BLACKLIST = ["SupportedLoadBalancerTypes"]
 
+
 def camel_case_to_snake_case(name: str):
     return CAMEL_CASE_PATTERN.sub("_", name).lower()
 
diff --git a/update_version_from_git.py b/update_version_from_git.py
index 94e9a498d..a6f644651 100644
--- a/update_version_from_git.py
+++ b/update_version_from_git.py
@@ -59,7 +59,7 @@ def migrate_version(new_version):
     )
     migrate_source_attribute(
         "version =",
-        to_this=f'version = {new_version}\n',
+        to_this=f"version = {new_version}\n",
         target_file=setupcfg,
     )
 


Test patch:
diff --git a/tests/test_athena/test_athena.py b/tests/test_athena/test_athena.py
index 0b6dad04a..cd5e8f2de 100644
--- a/tests/test_athena/test_athena.py
+++ b/tests/test_athena/test_athena.py
@@ -107,6 +107,19 @@ def test_start_query_execution():
     assert response["QueryExecutionId"] != sec_response["QueryExecutionId"]
 
 
+@mock_aws
+def test_start_query_execution_without_result_configuration():
+    client = boto3.client("athena", region_name="us-east-1")
+
+    create_basic_workgroup(client=client, name="athena_workgroup")
+    response = client.start_query_execution(
+        QueryString="query1",
+        QueryExecutionContext={"Database": "string"},
+        WorkGroup="athena_workgroup",
+    )
+    assert "QueryExecutionId" in response
+
+
 @mock_aws
 def test_start_query_validate_workgroup():
     client = boto3.client("athena", region_name="us-east-1")
diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py
index 87905a3ce..885ab4c2e 100644
--- a/tests/test_ec2/test_amis.py
+++ b/tests/test_ec2/test_amis.py
@@ -483,6 +483,14 @@ def test_ami_filters():
     )["Images"]
     assert imageA.id in [ami["ImageId"] for ami in amis_by_nonpublic]
 
+    amis_by_product_code = ec2.describe_images(
+        Filters=[
+            {"Name": "product-code", "Values": ["code123"]},
+            {"Name": "product-code.type", "Values": ["marketplace"]},
+        ]
+    )["Images"]
+    assert "ami-0b301ce3ce3475r4f" in [ami["ImageId"] for ami in amis_by_product_code]
+
 
 @mock_aws
 def test_ami_filtering_via_tag():
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
index 29f3848d1..dd9295645 100644
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -3650,6 +3650,84 @@ def test_list_tasks_with_filters():
     assert len(resp["taskArns"]) == 1
 
 
+@pytest.mark.parametrize(
+    "update_params",
+    [
+        {
+            "loadBalancers": [
+                {
+                    "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/mock-service-http",
+                    "loadBalancerName": "mock-service1",
+                    "containerName": "mock-container1",
+                    "containerPort": 80,
+                }
+            ]
+        },
+        {
+            "serviceRegistries": [
+                {
+                    "registryArn": "string",
+                    "port": 123,
+                    "containerName": "string",
+                    "containerPort": 123,
+                },
+                {
+                    "registryArn": "string2",
+                    "port": 1234,
+                    "containerName": "string2",
+                    "containerPort": 1234,
+                },
+            ],
+        },
+        {
+            "placementStrategy": [
+                {"type": "random", "field": "string"},
+            ],
+        },
+        {
+            "deploymentConfiguration": {
+                "deploymentCircuitBreaker": {"enable": True, "rollback": True},
+                "maximumPercent": 10,
+                "minimumHealthyPercent": 1,
+                "alarms": {
+                    "alarmNames": [
+                        "string",
+                    ],
+                    "enable": True,
+                    "rollback": True,
+                },
+            },
+        },
+    ],
+)
+@mock_aws
+def test_remove_tg(update_params):
+    # Setup
+    ecs_client = boto3.client("ecs", region_name=ECS_REGION)
+    cluster = "mock-cluster"
+    service = "mock-service"
+    ecs_client.create_cluster(clusterName=cluster)
+    ecs_client.create_service(
+        cluster=cluster,
+        serviceName=service,
+    )
+
+    # Execute
+    ecs_client.update_service(
+        cluster="arn:aws:ecs:us-east-1:123456789012:cluster/" + cluster,
+        service=service,
+        **update_params,
+    )
+    response = ecs_client.describe_services(cluster=cluster, services=[service])
+    response_svc = response["services"][0]
+    property_key = next(iter(update_params))
+    property_val = next(iter(update_params.values()))
+
+    # Verify
+    assert len(response_svc[property_key]) == len(property_val)
+    assert response_svc[property_key] == update_params[property_key]
+
+
 def setup_ecs(client, ec2):
     """test helper"""
     vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
diff --git a/tests/test_rekognition/test_rekognition.py b/tests/test_rekognition/test_rekognition.py
index 94239fcd5..6df5cc999 100644
--- a/tests/test_rekognition/test_rekognition.py
+++ b/tests/test_rekognition/test_rekognition.py
@@ -6,6 +6,7 @@ import string
 import boto3
 
 from moto import mock_aws
+from moto.moto_api._internal import mock_random
 
 # See our Development Tips on writing tests for hints on how to write good tests:
 # http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
@@ -116,3 +117,16 @@ def test_get_text_detection():
 
     assert resp["JobStatus"] == "SUCCEEDED"
     assert resp["TextDetections"][0]["TextDetection"]["DetectedText"] == "Hello world"
+
+
+@mock_aws
+def test_detect_custom_labels():
+    client = boto3.client("rekognition", region_name="us-east-2")
+    resp = client.detect_custom_labels(
+        Image={"S3Object": {"Bucket": "string", "Name": "name.jpg"}},
+        MaxResults=10,
+        MinConfidence=80,
+        ProjectVersionArn=mock_random.get_random_string(),
+    )
+    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert resp["CustomLabels"][0]["Name"] == "MyLogo"
diff --git a/tests/test_sagemaker/test_sagemaker_trial_component.py b/tests/test_sagemaker/test_sagemaker_trial_component.py
index 95cfdd7c7..fe1ad67cf 100644
--- a/tests/test_sagemaker/test_sagemaker_trial_component.py
+++ b/tests/test_sagemaker/test_sagemaker_trial_component.py
@@ -1,3 +1,4 @@
+import datetime
 import uuid
 
 import boto3
@@ -11,7 +12,7 @@ TEST_REGION_NAME = "us-east-1"
 
 
 @mock_aws
-def test_create__trial_component():
+def test_create_trial_component():
     client = boto3.client("sagemaker", region_name=TEST_REGION_NAME)
 
     trial_component_name = "some-trial-component-name"
@@ -20,16 +21,24 @@ def test_create__trial_component():
 
     assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
 
-    resp = client.list_trial_components()
+    resp = client.describe_trial_component(TrialComponentName=trial_component_name)
 
-    assert len(resp["TrialComponentSummaries"]) == 1
-    assert (
-        resp["TrialComponentSummaries"][0]["TrialComponentName"] == trial_component_name
-    )
-    assert resp["TrialComponentSummaries"][0]["TrialComponentArn"] == (
+    assert resp["TrialComponentName"] == trial_component_name
+    assert resp["TrialComponentArn"] == (
         f"arn:aws:sagemaker:{TEST_REGION_NAME}:{ACCOUNT_ID}"
         f":experiment-trial-component/{trial_component_name}"
     )
+    assert resp["DisplayName"] == trial_component_name
+    assert resp.get("Status") is None
+    assert resp.get("StartTime") is None
+    assert resp.get("EndTime") is None
+    assert resp.get("LastModifiedBy") == {}
+    assert resp.get("CreatedBy") == {}
+    assert resp.get("Parameters") == {}
+    assert resp.get("InputArtifacts") == {}
+    assert resp.get("OutputArtifacts") == {}
+    assert resp.get("Metrics") == []
+    assert resp.get("Sources") == []
 
 
 @mock_aws
@@ -263,3 +272,110 @@ def test_disassociate_trial_component():
         resp["TrialArn"]
         == f"arn:aws:sagemaker:{TEST_REGION_NAME}:{ACCOUNT_ID}:experiment-trial/does-not-exist"
     )
+
+
+@mock_aws
+def test_update_trial_component() -> None:
+    client = boto3.client("sagemaker", region_name=TEST_REGION_NAME)
+
+    trial_component_name = "some-trial-component-name"
+    client.create_trial_component(
+        TrialComponentName=trial_component_name,
+    )
+    given_status = {"PrimaryStatus": "InProgress", "Message": "a-message"}
+    given_display_name = "new-display-name"
+    given_parameters = {
+        "param1": {"StringValue": "param1-value", "NumberValue": 123.0},
+        "param2": {"StringValue": "param2-value", "NumberValue": 456.0},
+    }
+    given_input_artifacts = {
+        "artifact1": {"MediaType": "text/plain", "Value": "artifact1-value"}
+    }
+    given_output_artifacts = {
+        "artifact2": {"MediaType": "text/plain", "Value": "artifact2-value"}
+    }
+    given_start_date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc)
+    given_end_date = datetime.datetime(2021, 1, 2, tzinfo=datetime.timezone.utc)
+    resp_created = client.describe_trial_component(
+        TrialComponentName=trial_component_name
+    )
+
+    resp_update = client.update_trial_component(
+        TrialComponentName=trial_component_name,
+        DisplayName=given_display_name,
+        Status=given_status,
+        StartTime=given_start_date,
+        EndTime=given_end_date,
+        Parameters=given_parameters,
+        InputArtifacts=given_input_artifacts,
+        OutputArtifacts=given_output_artifacts,
+    )
+    resp_updated = client.describe_trial_component(
+        TrialComponentName=trial_component_name
+    )
+
+    assert (
+        resp_update["TrialComponentArn"]
+        == f"arn:aws:sagemaker:{TEST_REGION_NAME}:{ACCOUNT_ID}:experiment-trial-component/some-trial-component-name"
+    )
+    assert resp_updated["DisplayName"] == given_display_name
+    assert resp_updated["Status"] == given_status
+    assert resp_updated["StartTime"] == given_start_date
+    assert resp_updated["EndTime"] == given_end_date
+    assert resp_updated["Parameters"] == given_parameters
+    assert resp_updated["InputArtifacts"] == given_input_artifacts
+    assert resp_updated["OutputArtifacts"] == given_output_artifacts
+    assert resp_updated["LastModifiedTime"] >= resp_created["LastModifiedTime"]
+
+
+@mock_aws
+def test_update_trial_component_with_to_remove_options() -> None:
+    client = boto3.client("sagemaker", region_name=TEST_REGION_NAME)
+
+    trial_component_name = "some-trial-component-name"
+    client.create_trial_component(
+        TrialComponentName=trial_component_name,
+        Parameters={
+            "param1": {"StringValue": "param1-value", "NumberValue": 123.0},
+            "param2": {"StringValue": "param2-value", "NumberValue": 456.0},
+        },
+        InputArtifacts={
+            "artifact1": {"MediaType": "text/plain", "Value": "artifact1-value"}
+        },
+        OutputArtifacts={
+            "artifact2": {"MediaType": "text/plain", "Value": "artifact2-value"}
+        },
+    )
+
+    client.update_trial_component(
+        TrialComponentName=trial_component_name,
+        ParametersToRemove=["param1"],
+        InputArtifactsToRemove=["artifact1"],
+        OutputArtifactsToRemove=["artifact2"],
+    )
+    resp_updated = client.describe_trial_component(
+        TrialComponentName=trial_component_name
+    )
+
+    assert resp_updated["Parameters"].keys() == {"param2"}
+    assert resp_updated["InputArtifacts"] == {}
+    assert resp_updated["OutputArtifacts"] == {}
+
+
+@mock_aws
+def test_update_trial_component_should_return_an_error_if_trial_component_not_found() -> (
+    None
+):
+    client = boto3.client("sagemaker", region_name=TEST_REGION_NAME)
+
+    with pytest.raises(ClientError) as ex:
+        client.update_trial_component(
+            TrialComponentName="not-found",
+            DisplayName="new-display-name",
+        )
+
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+    assert ex.value.response["Error"]["Message"] == (
+        "Could not find trial component 'arn:aws:sagemaker:us-east-1:123456789012:experiment-trial-component/not-found'"
+    )
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
diff --git a/tests/test_stepfunctions/parser/__init__.py b/tests/test_stepfunctions/parser/__init__.py
index 34a513126..11d3e17d9 100644
--- a/tests/test_stepfunctions/parser/__init__.py
+++ b/tests/test_stepfunctions/parser/__init__.py
@@ -76,7 +76,7 @@ def verify_execution_result(
     )
     for _ in range(10):
         execution = client.describe_execution(executionArn=execution_arn)
-        if execution["status"] == expected_status:
+        if expected_status is None or execution["status"] == expected_status:
             result = _verify_result(client, execution, execution_arn)
             if result is not False:
                 client.delete_state_machine(stateMachineArn=state_machine_arn)
diff --git a/tests/test_stepfunctions/parser/templates/services/dynamodb_invalid_task_token.json b/tests/test_stepfunctions/parser/templates/services/dynamodb_invalid_task_token.json
new file mode 100644
index 000000000..a9e05c7d2
--- /dev/null
+++ b/tests/test_stepfunctions/parser/templates/services/dynamodb_invalid_task_token.json
@@ -0,0 +1,18 @@
+{
+"StartAt": "WaitForTransferFinished",
+"States": {
+    "WaitForTransferFinished": {
+        "Type": "Task",
+        "Resource": "arn:aws:states:::aws-sdk:dynamodb:updateItem.waitForTaskToken",
+        "Parameters": {
+            "TableName.$": "$.TableName",
+            "Key": {"id": {"S": "1"}},
+            "UpdateExpression": "SET StepFunctionTaskToken = :taskToken",
+            "ExpressionAttributeValues": {
+                ":taskToken": {"S": "$$.Task.Token"}
+            }
+        },
+        "End": true
+    }
+}
+}
\ No newline at end of file
diff --git a/tests/test_stepfunctions/parser/templates/services/dynamodb_task_token.json b/tests/test_stepfunctions/parser/templates/services/dynamodb_task_token.json
new file mode 100644
index 000000000..6e660d078
--- /dev/null
+++ b/tests/test_stepfunctions/parser/templates/services/dynamodb_task_token.json
@@ -0,0 +1,18 @@
+{
+"StartAt": "WaitForTransferFinished",
+"States": {
+    "WaitForTransferFinished": {
+        "Type": "Task",
+        "Resource": "arn:aws:states:::aws-sdk:dynamodb:updateItem.waitForTaskToken",
+        "Parameters": {
+            "TableName.$": "$.TableName",
+            "Key": {"id": {"S": "1"}},
+            "UpdateExpression": "SET StepFunctionTaskToken = :taskToken",
+            "ExpressionAttributeValues": {
+                ":taskToken": {"S.$": "$$.Task.Token"}
+            }
+        },
+        "End": true
+    }
+}
+}
\ No newline at end of file
diff --git a/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py b/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py
index eceb0dc28..1fbd708d0 100644
--- a/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py
+++ b/tests/test_stepfunctions/parser/test_stepfunctions_dynamodb_integration.py
@@ -100,6 +100,118 @@ def test_state_machine_calling_dynamodb_put(table_name=None, sleep_time=0):
     )
 
 
+@aws_verified
+@pytest.mark.aws_verified
+def test_state_machine_calling_dynamodb_put_wait_for_invalid_task_token(
+    table_name=None, sleep_time=0
+):
+    dynamodb = boto3.client("dynamodb", "us-east-1")
+    exec_input = {
+        "TableName": table_name,
+        "Item": {"data": {"S": "HelloWorld"}, "id": {"S": "id1"}},
+    }
+
+    tmpl_name = "services/dynamodb_invalid_task_token"
+
+    def _verify_result(client, execution, execution_arn):
+        if execution["status"] == "RUNNING":
+            items = dynamodb.scan(TableName=table_name)["Items"]
+            if len(items) > 0:
+                assert len(items) == 1
+                assert items[0]["id"] == {"S": "1"}
+                assert items[0]["StepFunctionTaskToken"] == {"S": "$$.Task.Token"}
+                # Because the TaskToken is not returned, we can't signal to SFN to finish the execution
+                # So let's just stop the execution manually
+                client.stop_execution(
+                    executionArn=execution_arn,
+                    error="Bad Example - no TaskToken available to continue this execution",
+                )
+                return True
+
+        return False
+
+    verify_execution_result(
+        _verify_result,
+        expected_status=None,
+        tmpl_name=tmpl_name,
+        exec_input=json.dumps(exec_input),
+        sleep_time=sleep_time,
+    )
+
+
+@aws_verified
+@pytest.mark.aws_verified
+def test_state_machine_calling_dynamodb_put_wait_for_task_token(
+    table_name=None, sleep_time=0
+):
+    dynamodb = boto3.client("dynamodb", "us-east-1")
+    exec_input = {"TableName": table_name, "Item": {"id": {"S": "id1"}}}
+    output = {"a": "b"}
+
+    tmpl_name = "services/dynamodb_task_token"
+
+    def _verify_result(client, execution, execution_arn):
+        if execution["status"] == "RUNNING":
+            items = dynamodb.scan(TableName=table_name)["Items"]
+            if len(items) > 0:
+                assert len(items) == 1
+                assert items[0]["id"] == {"S": "1"}
+                # Some random token
+                assert len(items[0]["StepFunctionTaskToken"]["S"]) > 25
+                token = items[0]["StepFunctionTaskToken"]["S"]
+                client.send_task_success(taskToken=token, output=json.dumps(output))
+
+        if execution["status"] == "SUCCEEDED":
+            assert json.loads(execution["output"]) == output
+            return True
+
+        return False
+
+    verify_execution_result(
+        _verify_result,
+        expected_status=None,
+        tmpl_name=tmpl_name,
+        exec_input=json.dumps(exec_input),
+        sleep_time=sleep_time,
+    )
+
+
+@aws_verified
+@pytest.mark.aws_verified
+def test_state_machine_calling_dynamodb_put_fail_task_token(
+    table_name=None, sleep_time=0
+):
+    dynamodb = boto3.client("dynamodb", "us-east-1")
+    exec_input = {"TableName": table_name, "Item": {"id": {"S": "id1"}}}
+
+    tmpl_name = "services/dynamodb_task_token"
+
+    def _verify_result(client, execution, execution_arn):
+        if execution["status"] == "RUNNING":
+            items = dynamodb.scan(TableName=table_name)["Items"]
+            if len(items) > 0:
+                assert len(items) == 1
+                assert items[0]["id"] == {"S": "1"}
+                # Some random token
+                assert len(items[0]["StepFunctionTaskToken"]["S"]) > 25
+                token = items[0]["StepFunctionTaskToken"]["S"]
+                client.send_task_failure(taskToken=token, error="test error")
+
+        if execution["status"] == "FAILED":
+            assert execution["error"] == "test error"
+            return True
+
+        return False
+
+    verify_execution_result(
+        _verify_result,
+        expected_status=None,
+        tmpl_name=tmpl_name,
+        exec_input=json.dumps(exec_input),
+        sleep_time=sleep_time,
+    )
+
+
 @aws_verified
 @pytest.mark.aws_verified
 def test_state_machine_calling_dynamodb_put_and_delete(table_name=None, sleep_time=0):