Repository: getmoto/moto
Pull request #7613 (2024-04-21 06:10:15)

chore: update EC2 Instance Offerings
Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action


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/resources/instance_type_offerings/availability-zone-id/eu-central-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json
index 5197f6e73..d5c9e659b 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/eu-central-2.json
@@ -627,6 +627,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "euc2-az1"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "euc2-az1"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "euc2-az1"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "euc2-az1"
@@ -1375,6 +1411,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "euc2-az2"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "euc2-az2"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "euc2-az2"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "euc2-az2"
@@ -2071,6 +2143,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "euc2-az3"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "euc2-az3"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "euc2-az3"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "euc2-az3"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json
index 59a1fdde5..9ce7dab36 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/eu-central-2.json
@@ -627,6 +627,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "eu-central-2a"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "eu-central-2a"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "eu-central-2a"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "eu-central-2a"
@@ -1375,6 +1411,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "eu-central-2b"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "eu-central-2b"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "eu-central-2b"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "eu-central-2b"
@@ -2071,6 +2143,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "eu-central-2c"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "eu-central-2c"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "eu-central-2c"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "eu-central-2c"
diff --git a/moto/ec2/resources/instance_type_offerings/region/eu-central-2.json b/moto/ec2/resources/instance_type_offerings/region/eu-central-2.json
index e2acd40f1..a1a041b21 100644
--- a/moto/ec2/resources/instance_type_offerings/region/eu-central-2.json
+++ b/moto/ec2/resources/instance_type_offerings/region/eu-central-2.json
@@ -627,6 +627,42 @@
   "InstanceType": "r6g.xlarge",
   "Location": "eu-central-2"
  },
+ {
+  "InstanceType": "r6gd.12xlarge",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.16xlarge",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.2xlarge",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.4xlarge",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.8xlarge",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.large",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.medium",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.metal",
+  "Location": "eu-central-2"
+ },
+ {
+  "InstanceType": "r6gd.xlarge",
+  "Location": "eu-central-2"
+ },
  {
   "InstanceType": "r6i.12xlarge",
   "Location": "eu-central-2"
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/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_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