Repository: getmoto/moto (python)

Issue:
Inconsistent behavior of SageMaker Client Search Method for Model Packages
## Environment Information

  Python Version: 3.10.14
  boto3 == 1.24.4
  pytest == 8.3.2
  moto == 5.0.13

## Problem Description
I believe this issue is very similar to [#7898](https://github.com/getmoto/moto/issues/7898). I'm also using moto to test the retrieval of models, this time model packages instead of model package groups. I've created some model packages using `create_model_package` and when using the Resource `ModelPackage` in `sagemaker_client.search`, no model packages were returned. On the other hand, I had results returned when using the Resource `ModelPackageGroup`. 

I also tried to `list_model_packages` with the same set of created model packages and had two outcomes: 
1. When using `ModelPackageGroupName`: the models that were under the provided model package group were returned.
2. When not using any parameters: an empty list was returned. 

## Minimal Example

**search_model_package.py**

```python
import boto3

class ModelPackageFinder:
    def __init__(self):
        self.sm_client = boto3.client('sagemaker')
    
    def get_model_groups_with_search_method(self):
        search_results = self.sm_client.search(
            Resource='ModelPackage',
            SearchExpression={
                'Filters': [{
                    'Name': 'ModelPackageName', 
                    'Operator': 'Contains', 
                    'Value': 'model-test'}]}
        )
        return search_results

    def get_model_packages_with_list_model_packages_method(self):
        list_model_results = self.sm_client.list_model_packages()
        return list_model_results
    
    def get_model_packages_with_list_model_packages_method_and_group(self):
        list_model_results = self.sm_client.list_model_packages(ModelPackageGroupName="model-test_DEV")
        return list_model_results
```

**test_model_groups.py**
```python
import boto3
from moto import mock_aws
from search_model_package import ModelPackageFinder
from pprint import pprint

MOCK_SOURCE_ROLE_ARN = 'arn:aws:iam::123456789012:role/DSSourceRole'


def mock_model_groups(sm_client):
    sm_client.create_model_package_group(
        ModelPackageGroupName='model-test_DEV',
        ModelPackageGroupDescription='Model group in DEV environment.',
        Tags=[
            {
                'Key': 'RegisteredBySourceRoleArn',
                'Value': MOCK_SOURCE_ROLE_ARN
            }
        ]
    )
    sm_client.create_model_package_group(
        ModelPackageGroupName='model-test_PROD',
        ModelPackageGroupDescription='Model group in PROD environment.',
        Tags=[
            {
                'Key': 'RegisteredBySourceRoleArn',
                'Value': MOCK_SOURCE_ROLE_ARN
            }
        ]
    )

def mock_model_packages(sm_client):
    sm_client.create_model_package(
        ModelPackageGroupName='model-test_DEV',
        ModelPackageDescription="Model package version in DEV environment.",
        Tags=[
            {"Key": "RegisteredUsingSofiaSDK", "Value": "true"},
            {"Key": "RegisteredBySourceRoleArn", "Value": MOCK_SOURCE_ROLE_ARN},
        ],
    )
    sm_client.create_model_package(
        ModelPackageGroupName='model-test_PROD',
        ModelPackageDescription="Model package version in PROD environment.",
        Tags=[
            {"Key": "RegisteredUsingSofiaSDK", "Value": "true"},
            {"Key": "RegisteredBySourceRoleArn", "Value": MOCK_SOURCE_ROLE_ARN},
        ],
    )
    sm_client.create_model_package(
        ModelPackageGroupName='model-test_DEV',
        ModelPackageDescription="Model package version in DEV environment with change in hyperparameter.",
        Tags=[
            {"Key": "RegisteredUsingSofiaSDK", "Value": "true"},
            {"Key": "RegisteredBySourceRoleArn", "Value": MOCK_SOURCE_ROLE_ARN},
        ]
    )

@mock_aws
def test_search_function():
    sm_client = boto3.client('sagemaker')
    mock_model_groups(sm_client)
    mock_model_packages(sm_client)
    model_group_finder = ModelPackageFinder()
    model_groups = model_group_finder.get_model_groups_with_search_method()
    print('Sagemaker Search Function: ')
    pprint(model_groups)


@mock_aws
def test_list_function():
    sm_client = boto3.client('sagemaker')
    mock_model_groups(sm_client)
    mock_model_packages(sm_client)
    model_group_finder = ModelPackageFinder()
    model_groups = model_group_finder.get_model_packages_with_list_model_packages_method()
    print('List Model Package Function')
    pprint(model_groups)

@mock_aws
def test_list_function_with_parameter():
    sm_client = boto3.client('sagemaker')
    mock_model_groups(sm_client)
    mock_model_packages(sm_client)
    model_group_finder = ModelPackageFinder()
    model_groups = model_group_finder.get_model_packages_with_list_model_packages_method_and_group()
    print('List Model Package Function With Package Group as Parameter')
    pprint(model_groups)
```

When running `pytest -s test_model_packages.py`, the following print statements were shown in the console:

`tests/test_model_packages.py Sagemaker Search Function: `
```python
{'ResponseMetadata': {'HTTPHeaders': {'date': 'Tue, 24 Sep 2024 16:37:25 GMT',
                                      'server': 'amazon.com',
                                      'x-amzn-requestid': 'LHRdZh1KJTBwIxP4aRRTLsFWN06Git5j9i3tx0ZoroLkJ8AC8aXZ'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'LHRdZh1KJTBwIxP4aRRTLsFWN06Git5j9i3tx0ZoroLkJ8AC8aXZ',
                      'RetryAttempts': 0},
 'Results': []}
```

`.List Model Package Function`
```python

{'ModelPackageSummaryList': [],
 'ResponseMetadata': {'HTTPHeaders': {'date': 'Tue, 24 Sep 2024 16:37:25 GMT',
                                      'server': 'amazon.com',
                                      'x-amzn-requestid': 'A7ZD2v5G8YWUvjCBRYa66aqmdQG2qWiSdCA72g0CD2nDBMIvvpQp'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'A7ZD2v5G8YWUvjCBRYa66aqmdQG2qWiSdCA72g0CD2nDBMIvvpQp',
                      'RetryAttempts': 0}}
```

`.List Model Package Function With Package Group as Parameter`
```python
{'ModelPackageSummaryList': [{'CreationTime': datetime.datetime(2024, 9, 24, 19, 37, 25, 956457, tzinfo=tzutc()),
                              'ModelPackageArn': 'arn:aws:sagemaker:us-east-1:123456789012:model-package/model-test_dev/1',
                              'ModelPackageDescription': 'Model package '
                                                         'version in DEV '
                                                         'environment.',
                              'ModelPackageGroupName': 'model-test_DEV',
                              'ModelPackageStatus': 'Completed',
                              'ModelPackageVersion': 1},
                             {'CreationTime': datetime.datetime(2024, 9, 24, 19, 37, 25, 957802, tzinfo=tzutc()),
                              'ModelPackageArn': 'arn:aws:sagemaker:us-east-1:123456789012:model-package/model-test_dev/2',
                              'ModelPackageDescription': 'Model package '
                                                         'version in DEV '
                                                         'environment with '
                                                         'change in '
                                                         'hyperparameter.',
                              'ModelPackageGroupName': 'model-test_DEV',
                              'ModelPackageStatus': 'Completed',
                              'ModelPackageVersion': 2}],
 'ResponseMetadata': {'HTTPHeaders': {'date': 'Tue, 24 Sep 2024 16:37:25 GMT',
                                      'server': 'amazon.com',
                                      'x-amzn-requestid': 'OTR8HEhDYNVT6N5Z7AaC5fn8ZCJAfBfbmjiJeFvhzopSJFWNcI8e'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'OTR8HEhDYNVT6N5Z7AaC5fn8ZCJAfBfbmjiJeFvhzopSJFWNcI8e',
                      'RetryAttempts': 0}}
```


As seen above, the `sm_client.search` did not return any results when using the `ModelPackage` Resource. The `sm_client.list_model_packages` method also did not return anything when passing no parameters, but it did when passing the `ModelPackageGroupName` parameter.


Pull request:
SageMaker: search() now also supports ModelPackages/Pipelines/Jobs/Executions
Fixes #8155 

We now use the existing `get_response_object()` for each resource, so we now automatically return all attributes (instead of only the hardcoded ones).

Patch:
diff --git a/moto/sagemaker/models.py b/moto/sagemaker/models.py
index 9d2fc7ec4..4b38407af 100644
--- a/moto/sagemaker/models.py
+++ b/moto/sagemaker/models.py
@@ -1093,6 +1093,7 @@ class ModelPackageGroup(BaseObject):
             "ModelPackageGroupDescription",
             "CreationTime",
             "ModelPackageGroupStatus",
+            "Tags",
         ]
         response = {k: v for k, v in response_object.items() if k in response_values}
         response["ModelPackageGroupArn"] = response.pop("Arn")
@@ -3072,21 +3073,21 @@ class SageMakerModelBackend(BaseBackend):
         """
         next_index = None
 
-        valid_resources = [
-            "Pipeline",
-            "ModelPackageGroup",
-            "TrainingJob",
-            "ExperimentTrialComponent",
-            "FeatureGroup",
-            "Endpoint",
-            "PipelineExecution",
-            "Project",
-            "ExperimentTrial",
-            "Image",
-            "ImageVersion",
-            "ModelPackage",
-            "Experiment",
-        ]
+        valid_resources = {
+            "Pipeline": self.pipelines.values(),
+            "ModelPackageGroup": self.model_package_groups.values(),
+            "TrainingJob": self.training_jobs.values(),
+            "ExperimentTrialComponent": self.trial_components.values(),
+            "FeatureGroup": self.feature_groups.values(),
+            "Endpoint": self.endpoints.values(),
+            "PipelineExecution": self.pipeline_executions.values(),
+            "Project": [],
+            "ExperimentTrial": self.trials.values(),
+            "Image": [],
+            "ImageVersion": [],
+            "ModelPackage": self.model_packages.values(),
+            "Experiment": self.experiments.values(),
+        }
 
         if resource not in valid_resources:
             raise AWSValidationException(
@@ -3148,73 +3149,18 @@ class SageMakerModelBackend(BaseBackend):
             "Results": [],
             "NextToken": str(next_index) if next_index is not None else None,
         }
-        if resource == "Experiment":
-            experiments_fetched = list(self.experiments.values())
-
-            experiment_summaries = [
-                {
-                    "ExperimentName": experiment_data.experiment_name,
-                    "ExperimentArn": experiment_data.arn,
-                    "CreationTime": experiment_data.creation_time,
-                    "LastModifiedTime": experiment_data.last_modified_time,
-                }
-                for experiment_data in experiments_fetched
-                if evaluate_search_expression(experiment_data)
-            ]
-
-            for experiment_summary in experiment_summaries:
-                result["Results"].append({"Experiment": experiment_summary})
-
-        if resource == "ExperimentTrial":
-            trials_fetched = list(self.trials.values())
-
-            trial_summaries = [
-                {
-                    "TrialName": trial_data.trial_name,
-                    "TrialArn": trial_data.arn,
-                    "CreationTime": trial_data.creation_time,
-                    "LastModifiedTime": trial_data.last_modified_time,
-                }
-                for trial_data in trials_fetched
-                if evaluate_search_expression(trial_data)
-            ]
-
-            for trial_summary in trial_summaries:
-                result["Results"].append({"Trial": trial_summary})
-
-        if resource == "ExperimentTrialComponent":
-            trial_components_fetched = list(self.trial_components.values())
-
-            trial_component_summaries = [
-                {
-                    "TrialComponentName": trial_component_data.trial_component_name,
-                    "TrialComponentArn": trial_component_data.arn,
-                    "CreationTime": trial_component_data.creation_time,
-                    "LastModifiedTime": trial_component_data.last_modified_time,
-                }
-                for trial_component_data in trial_components_fetched
-                if evaluate_search_expression(trial_component_data)
-            ]
-
-            for trial_component_summary in trial_component_summaries:
-                result["Results"].append({"TrialComponent": trial_component_summary})
-
-        if resource == "ModelPackageGroup":
-            package_groups = [
-                {
-                    "ModelPackageGroupArn": group.arn,
-                    "ModelPackageGroupDescription": group.model_package_group_description,
-                    "CreationTime": group.creation_time.strftime("%Y-%m-%d %H:%M:%S"),
-                    "ModelPackageGroupName": group.model_package_group_name,
-                    "ModelPackageGroupStatus": group.model_package_group_status,
-                    "Tags": group.tags,
-                }
-                for group in self.model_package_groups.values()
-                if evaluate_search_expression(group)
-            ]
+        # ResourceName, ResultName, Resources
+        result_names = {
+            "ExperimentTrial": "Trial",
+            "ExperimentTrialComponent": "TrialComponent",
+        }
+        resources_found = [
+            x for x in valid_resources[resource] if evaluate_search_expression(x)
+        ]
+        result_name = result_names.get(resource, resource)
+        for found in resources_found:
+            result["Results"].append({result_name: found.gen_response_object()})
 
-            for group in package_groups:
-                result["Results"].append({"ModelPackageGroup": group})
         return result
 
     def delete_experiment(self, experiment_name: str) -> None:


Test patch:
diff --git a/tests/test_sagemaker/test_sagemaker_search.py b/tests/test_sagemaker/test_sagemaker_search.py
index 615227a20..ef0667522 100644
--- a/tests/test_sagemaker/test_sagemaker_search.py
+++ b/tests/test_sagemaker/test_sagemaker_search.py
@@ -129,6 +129,35 @@ def test_search_experiments_with_filter(sagemaker_client):
     )
 
 
+def test_search_model_packages(sagemaker_client):
+    group_name = "mpg_DEV"
+    sagemaker_client.create_model_package_group(
+        ModelPackageGroupName=group_name,
+        ModelPackageGroupDescription="test-model-package-group-description",
+    )
+    sagemaker_client.create_model_package(ModelPackageGroupName=group_name)
+    sagemaker_client.create_model_package(ModelPackageName="test")
+    sagemaker_client.create_model_package(ModelPackageName="prod")
+
+    _verify_search_results(
+        {"Name": "ModelPackageName", "Operator": "Contains", "Value": "test"},
+        1,
+        resource="ModelPackage",
+    )
+
+    _verify_search_results(
+        {"Name": "ModelPackageName", "Operator": "Contains", "Value": "prod"},
+        1,
+        resource="ModelPackage",
+    )
+
+    _verify_search_results(
+        {"Name": "ModelPackageName", "Operator": "Contains", "Value": "staging"},
+        0,
+        resource="ModelPackage",
+    )
+
+
 def test_search_model_package_groups_with_filter(sagemaker_client):
     sagemaker_client.create_model_package_group(
         ModelPackageGroupName="mpg_DEV",