Repository: getmoto/moto
Pull request #5958 (2023-02-21 21:40:05)

Core: Reset data when mock stops


Patch:
diff --git a/moto/core/models.py b/moto/core/models.py
index 68497694c..58c3b24f3 100644
--- a/moto/core/models.py
+++ b/moto/core/models.py
@@ -64,10 +64,12 @@ class BaseMockAWS:
         if self.__class__.nested_count == 0:
             self.reset()  # type: ignore[attr-defined]
 
-    def __call__(self, func: Callable[..., Any], reset: bool = True) -> Any:
+    def __call__(
+        self, func: Callable[..., Any], reset: bool = True, remove_data: bool = True
+    ) -> Any:
         if inspect.isclass(func):
             return self.decorate_class(func)
-        return self.decorate_callable(func, reset)
+        return self.decorate_callable(func, reset, remove_data)
 
     def __enter__(self) -> "BaseMockAWS":
         self.start()
@@ -89,7 +91,7 @@ class BaseMockAWS:
 
         self.enable_patching(reset)  # type: ignore[attr-defined]
 
-    def stop(self) -> None:
+    def stop(self, remove_data: bool = True) -> None:
         self.__class__.nested_count -= 1
 
         if self.__class__.nested_count < 0:
@@ -105,18 +107,23 @@ class BaseMockAWS:
                     pass
                 self.unmock_env_variables()
                 self.__class__.mocks_active = False
-                reset_model_data()
+                if remove_data:
+                    # Reset the data across all backends
+                    for backend in self.backends.values():
+                        backend.reset()
+                    # Remove references to all model instances that were created
+                    reset_model_data()
             self.disable_patching()  # type: ignore[attr-defined]
 
     def decorate_callable(
-        self, func: Callable[..., CALLABLE_RETURN], reset: bool
+        self, func: Callable[..., CALLABLE_RETURN], reset: bool, remove_data: bool
     ) -> Callable[..., CALLABLE_RETURN]:
         def wrapper(*args: Any, **kwargs: Any) -> CALLABLE_RETURN:
             self.start(reset=reset)
             try:
                 result = func(*args, **kwargs)
             finally:
-                self.stop()
+                self.stop(remove_data=remove_data)
             return result
 
         functools.update_wrapper(wrapper, func)
@@ -173,15 +180,18 @@ class BaseMockAWS:
                 # Special case for UnitTests-class
                 is_test_method = attr.startswith(unittest.TestLoader.testMethodPrefix)
                 should_reset = False
+                should_remove_data = False
                 if attr in ["setUp", "setup_method"]:
                     should_reset = True
                 elif not has_setup_method and is_test_method:
                     should_reset = True
+                    should_remove_data = True
                 else:
                     # Method is unrelated to the test setup
                     # Method is a test, but was already reset while executing the setUp-method
                     pass
-                setattr(klass, attr, self(attr_value, reset=should_reset))
+                kwargs = {"reset": should_reset, "remove_data": should_remove_data}
+                setattr(klass, attr, self(attr_value, **kwargs))
             except TypeError:
                 # Sometimes we can't set this for built-in types
                 continue
diff --git a/moto/dynamodb/models/table.py b/moto/dynamodb/models/table.py
index b520e24f4..1be961410 100644
--- a/moto/dynamodb/models/table.py
+++ b/moto/dynamodb/models/table.py
@@ -843,6 +843,12 @@ class Table(CloudFormationModel):
             if passes_all_conditions:
                 results.append(item)
 
+        results = copy.deepcopy(results)
+        if index_name:
+            index = self.get_index(index_name)
+            for result in results:
+                index.project(result)
+
         results, last_evaluated_key = self._trim_results(
             results, limit, exclusive_start_key, scanned_index=index_name
         )
diff --git a/moto/eks/exceptions.py b/moto/eks/exceptions.py
index f25a5fcd6..61ae17c3b 100644
--- a/moto/eks/exceptions.py
+++ b/moto/eks/exceptions.py
@@ -1,16 +1,17 @@
 import json
+from typing import Any, Dict, Tuple
 
 from moto.core.exceptions import AWSError
 
 
 class EKSError(AWSError):
-    def __init__(self, **kwargs):
-        super(AWSError, self).__init__(error_type=self.TYPE, message="")
+    def __init__(self, **kwargs: Any):
+        super(AWSError, self).__init__(error_type=self.TYPE, message="")  # type: ignore
         self.description = json.dumps(kwargs)
         self.headers = {"status": self.STATUS, "x-amzn-ErrorType": self.TYPE}
         self.code = self.STATUS
 
-    def response(self):
+    def response(self) -> Tuple[int, Dict[str, Any], str]:  # type: ignore[override]
         return self.STATUS, self.headers, self.description
 
 
diff --git a/moto/eks/models.py b/moto/eks/models.py
index 30247c069..74df856df 100644
--- a/moto/eks/models.py
+++ b/moto/eks/models.py
@@ -1,4 +1,5 @@
 from datetime import datetime
+from typing import Any, Dict, List, Optional, Tuple, Iterator
 
 from moto.core import BaseBackend, BackendDict
 from moto.core.utils import iso_8601_datetime_without_milliseconds
@@ -52,7 +53,7 @@ DEFAULT_AMI_TYPE = "AL2_x86_64"
 DEFAULT_CAPACITY_TYPE = "ON_DEMAND"
 DEFAULT_DISK_SIZE = "20"
 DEFAULT_INSTANCE_TYPES = ["t3.medium"]
-DEFAULT_NODEGROUP_HEALTH = {"issues": []}
+DEFAULT_NODEGROUP_HEALTH: Dict[str, Any] = {"issues": []}
 DEFAULT_RELEASE_VERSION = "1.19.8-20210414"
 DEFAULT_REMOTE_ACCESS = {"ec2SshKey": "eksKeypair"}
 DEFAULT_SCALING_CONFIG = {"minSize": 2, "maxSize": 2, "desiredSize": 2}
@@ -91,28 +92,28 @@ NODEGROUP_NOT_FOUND_MSG = "No node group found for name: {nodegroupName}."
 class Cluster:
     def __init__(
         self,
-        name,
-        role_arn,
-        resources_vpc_config,
-        account_id,
-        region_name,
-        aws_partition,
-        version=None,
-        kubernetes_network_config=None,
-        logging=None,
-        client_request_token=None,
-        tags=None,
-        encryption_config=None,
+        name: str,
+        role_arn: str,
+        resources_vpc_config: Dict[str, Any],
+        account_id: str,
+        region_name: str,
+        aws_partition: str,
+        version: Optional[str] = None,
+        kubernetes_network_config: Optional[Dict[str, str]] = None,
+        logging: Optional[Dict[str, Any]] = None,
+        client_request_token: Optional[str] = None,
+        tags: Optional[Dict[str, str]] = None,
+        encryption_config: Optional[List[Dict[str, Any]]] = None,
     ):
         if encryption_config is None:
             encryption_config = []
         if tags is None:
             tags = dict()
 
-        self.nodegroups = dict()
+        self.nodegroups: Dict[str, ManagedNodegroup] = dict()
         self.nodegroup_count = 0
 
-        self.fargate_profiles = dict()
+        self.fargate_profiles: Dict[str, FargateProfile] = dict()
         self.fargate_profile_count = 0
 
         self.arn = CLUSTER_ARN_TEMPLATE.format(
@@ -141,7 +142,7 @@ class Cluster:
         self.role_arn = role_arn
         self.tags = tags
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Tuple[str, Any]]:
         yield "name", self.name
         yield "arn", self.arn
         yield "createdAt", self.creation_date
@@ -159,23 +160,23 @@ class Cluster:
         yield "tags", self.tags
         yield "encryptionConfig", self.encryption_config
 
-    def isActive(self):
+    def isActive(self) -> bool:
         return self.status == "ACTIVE"
 
 
 class FargateProfile:
     def __init__(
         self,
-        cluster_name,
-        fargate_profile_name,
-        pod_execution_role_arn,
-        selectors,
-        account_id,
-        region_name,
-        aws_partition,
-        client_request_token=None,
-        subnets=None,
-        tags=None,
+        cluster_name: str,
+        fargate_profile_name: str,
+        pod_execution_role_arn: str,
+        selectors: List[Dict[str, Any]],
+        account_id: str,
+        region_name: str,
+        aws_partition: str,
+        client_request_token: Optional[str] = None,
+        subnets: Optional[List[str]] = None,
+        tags: Optional[Dict[str, str]] = None,
     ):
         if subnets is None:
             subnets = list()
@@ -202,7 +203,7 @@ class FargateProfile:
         self.subnets = subnets
         self.tags = tags
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Tuple[str, Any]]:
         yield "clusterName", self.cluster_name
         yield "createdAt", self.created_at
         yield "fargateProfileArn", self.fargate_profile_arn
@@ -217,33 +218,33 @@ class FargateProfile:
 class ManagedNodegroup:
     def __init__(
         self,
-        cluster_name,
-        node_role,
-        nodegroup_name,
-        subnets,
-        account_id,
-        region_name,
-        aws_partition,
-        scaling_config=None,
-        disk_size=None,
-        instance_types=None,
-        ami_type=None,
-        remote_access=None,
-        labels=None,
-        taints=None,
-        tags=None,
-        client_request_token=None,
-        launch_template=None,
-        capacity_type=None,
-        version=None,
-        release_version=None,
+        cluster_name: str,
+        node_role: str,
+        nodegroup_name: str,
+        subnets: List[str],
+        account_id: str,
+        region_name: str,
+        aws_partition: str,
+        scaling_config: Optional[Dict[str, int]] = None,
+        disk_size: Optional[int] = None,
+        instance_types: Optional[List[str]] = None,
+        ami_type: Optional[str] = None,
+        remote_access: Optional[Dict[str, Any]] = None,
+        labels: Optional[Dict[str, str]] = None,
+        taints: Optional[List[Dict[str, str]]] = None,
+        tags: Optional[Dict[str, str]] = None,
+        client_request_token: Optional[str] = None,
+        launch_template: Optional[Dict[str, str]] = None,
+        capacity_type: Optional[str] = None,
+        version: Optional[str] = None,
+        release_version: Optional[str] = None,
     ):
         if tags is None:
             tags = dict()
         if labels is None:
             labels = dict()
         if taints is None:
-            taints = dict()
+            taints = []
 
         self.uuid = str(random.uuid4())
         self.arn = NODEGROUP_ARN_TEMPLATE.format(
@@ -291,19 +292,19 @@ class ManagedNodegroup:
             ec2 = ec2_backends[account_id][region_name]
 
             template = None
-            if "name" in self.launch_template:
-                name = self.launch_template["name"]
+            if "name" in self.launch_template:  # type: ignore
+                name = self.launch_template["name"]  # type: ignore
                 template = ec2.describe_launch_templates(template_names=[name])[0]
-            elif "id" in self.launch_template:
-                _id = self.launch_template["id"]
+            elif "id" in self.launch_template:  # type: ignore
+                _id = self.launch_template["id"]  # type: ignore
                 template = ec2.describe_launch_templates(template_ids=[_id])[0]
 
-            self.launch_template["id"] = template.id
-            self.launch_template["name"] = template.name
+            self.launch_template["id"] = template.id  # type: ignore
+            self.launch_template["name"] = template.name  # type: ignore
         except:  # noqa: E722 Do not use bare except
             pass
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[Tuple[str, Any]]:
         yield "nodegroupName", self.nodegroup_name
         yield "nodegroupArn", self.arn
         yield "clusterName", self.cluster_name
@@ -329,24 +330,24 @@ class ManagedNodegroup:
 
 
 class EKSBackend(BaseBackend):
-    def __init__(self, region_name, account_id):
+    def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
-        self.clusters = dict()
+        self.clusters: Dict[str, Cluster] = dict()
         self.cluster_count = 0
         self.partition = get_partition(region_name)
 
     def create_cluster(
         self,
-        name,
-        role_arn,
-        resources_vpc_config,
-        version=None,
-        kubernetes_network_config=None,
-        logging=None,
-        client_request_token=None,
-        tags=None,
-        encryption_config=None,
-    ):
+        name: str,
+        role_arn: str,
+        resources_vpc_config: Dict[str, Any],
+        version: Optional[str] = None,
+        kubernetes_network_config: Optional[Dict[str, str]] = None,
+        logging: Optional[Dict[str, Any]] = None,
+        client_request_token: Optional[str] = None,
+        tags: Optional[Dict[str, str]] = None,
+        encryption_config: Optional[List[Dict[str, Any]]] = None,
+    ) -> Cluster:
         if name in self.clusters:
             # Cluster exists.
             raise ResourceInUseException(
@@ -377,14 +378,14 @@ class EKSBackend(BaseBackend):
 
     def create_fargate_profile(
         self,
-        fargate_profile_name,
-        cluster_name,
-        selectors,
-        pod_execution_role_arn,
-        subnets=None,
-        client_request_token=None,
-        tags=None,
-    ):
+        fargate_profile_name: str,
+        cluster_name: str,
+        selectors: List[Dict[str, Any]],
+        pod_execution_role_arn: str,
+        subnets: Optional[List[str]] = None,
+        client_request_token: Optional[str] = None,
+        tags: Optional[Dict[str, str]] = None,
+    ) -> FargateProfile:
         try:
             # Cluster exists.
             cluster = self.clusters[cluster_name]
@@ -431,24 +432,24 @@ class EKSBackend(BaseBackend):
 
     def create_nodegroup(
         self,
-        cluster_name,
-        node_role,
-        nodegroup_name,
-        subnets,
-        scaling_config=None,
-        disk_size=None,
-        instance_types=None,
-        ami_type=None,
-        remote_access=None,
-        labels=None,
-        taints=None,
-        tags=None,
-        client_request_token=None,
-        launch_template=None,
-        capacity_type=None,
-        version=None,
-        release_version=None,
-    ):
+        cluster_name: str,
+        node_role: str,
+        nodegroup_name: str,
+        subnets: List[str],
+        scaling_config: Optional[Dict[str, int]] = None,
+        disk_size: Optional[int] = None,
+        instance_types: Optional[List[str]] = None,
+        ami_type: Optional[str] = None,
+        remote_access: Optional[Dict[str, Any]] = None,
+        labels: Optional[Dict[str, str]] = None,
+        taints: Optional[List[Dict[str, str]]] = None,
+        tags: Optional[Dict[str, str]] = None,
+        client_request_token: Optional[str] = None,
+        launch_template: Optional[Dict[str, str]] = None,
+        capacity_type: Optional[str] = None,
+        version: Optional[str] = None,
+        release_version: Optional[str] = None,
+    ) -> ManagedNodegroup:
         try:
             # Cluster exists.
             cluster = self.clusters[cluster_name]
@@ -506,7 +507,7 @@ class EKSBackend(BaseBackend):
         cluster.nodegroup_count += 1
         return nodegroup
 
-    def describe_cluster(self, name):
+    def describe_cluster(self, name: str) -> Cluster:
         try:
             # Cluster exists.
             return self.clusters[name]
@@ -520,7 +521,9 @@ class EKSBackend(BaseBackend):
                 message=CLUSTER_NOT_FOUND_MSG.format(clusterName=name),
             )
 
-    def describe_fargate_profile(self, cluster_name, fargate_profile_name):
+    def describe_fargate_profile(
+        self, cluster_name: str, fargate_profile_name: str
+    ) -> FargateProfile:
         try:
             # Cluster exists.
             cluster = self.clusters[cluster_name]
@@ -548,7 +551,9 @@ class EKSBackend(BaseBackend):
                 ),
             )
 
-    def describe_nodegroup(self, cluster_name, nodegroup_name):
+    def describe_nodegroup(
+        self, cluster_name: str, nodegroup_name: str
+    ) -> ManagedNodegroup:
         try:
             # Cluster exists.
             cluster = self.clusters[cluster_name]
@@ -574,7 +579,7 @@ class EKSBackend(BaseBackend):
                 message=NODEGROUP_NOT_FOUND_MSG.format(nodegroupName=nodegroup_name),
             )
 
-    def delete_cluster(self, name):
+    def delete_cluster(self, name: str) -> Cluster:
         try:
             # Cluster exists.
             validate_safe_to_delete(self.clusters[name])
@@ -592,7 +597,9 @@ class EKSBackend(BaseBackend):
         self.cluster_count -= 1
         return result
 
-    def delete_fargate_profile(self, cluster_name, fargate_profile_name):
+    def delete_fargate_profile(
+        self, cluster_name: str, fargate_profile_name: str
+    ) -> FargateProfile:
         try:
             # Cluster exists.
             cluster = self.clusters[cluster_name]
@@ -623,7 +630,9 @@ class EKSBackend(BaseBackend):
         cluster.fargate_profile_count -= 1
         return deleted_fargate_profile
 
-    def delete_nodegroup(self, cluster_name, nodegroup_name):
+    def delete_nodegroup(
+        self, cluster_name: str, nodegroup_name: str
+    ) -> ManagedNodegroup:
         try:
             # Cluster exists.
             cluster = self.clusters[cluster_name]
@@ -652,7 +661,7 @@ class EKSBackend(BaseBackend):
         cluster.nodegroup_count -= 1
         return result
 
-    def tag_resource(self, resource_arn, tags):
+    def tag_resource(self, resource_arn: str, tags: Dict[str, str]) -> None:
         """
         This function currently will tag an EKS cluster only.  It does not tag a managed node group
         """
@@ -673,9 +682,8 @@ class EKSBackend(BaseBackend):
                 message="An error occurred (NotFoundException) when calling the TagResource operation: Resource was not found",
             )
         cluster.tags.update(tags)
-        return ""
 
-    def untag_resource(self, resource_arn, tag_keys):
+    def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
         """
         This function currently will remove tags on an EKS cluster only.  It does not remove tags from a managed node group
         """
@@ -700,9 +708,8 @@ class EKSBackend(BaseBackend):
         for name in tag_keys:
             if name in cluster.tags:
                 del cluster.tags[name]
-        return ""
 
-    def list_tags_for_resource(self, resource_arn):
+    def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
         """
         This function currently will list tags on an EKS cluster only.  It does not list tags from a managed node group
         """
@@ -724,19 +731,29 @@ class EKSBackend(BaseBackend):
             )
         return cluster.tags
 
-    def list_clusters(self, max_results, next_token):
-        return paginated_list(self.clusters.keys(), max_results, next_token)
+    def list_clusters(
+        self, max_results: int, next_token: Optional[str]
+    ) -> Tuple[List[Cluster], Optional[Cluster]]:
+        return paginated_list(list(self.clusters.keys()), max_results, next_token)
 
-    def list_fargate_profiles(self, cluster_name, max_results, next_token):
+    def list_fargate_profiles(
+        self, cluster_name: str, max_results: int, next_token: Optional[str]
+    ) -> Tuple[List[FargateProfile], Optional[FargateProfile]]:
         cluster = self.clusters[cluster_name]
-        return paginated_list(cluster.fargate_profiles.keys(), max_results, next_token)
+        return paginated_list(
+            list(cluster.fargate_profiles.keys()), max_results, next_token
+        )
 
-    def list_nodegroups(self, cluster_name, max_results, next_token):
+    def list_nodegroups(
+        self, cluster_name: str, max_results: int, next_token: Optional[str]
+    ) -> Tuple[List[ManagedNodegroup], Optional[ManagedNodegroup]]:
         cluster = self.clusters[cluster_name]
-        return paginated_list(cluster.nodegroups.keys(), max_results, next_token)
+        return paginated_list(list(cluster.nodegroups.keys()), max_results, next_token)
 
 
-def paginated_list(full_list, max_results, next_token):
+def paginated_list(
+    full_list: List[Any], max_results: int, next_token: Optional[str]
+) -> Tuple[List[Any], Optional[Any]]:
     """
     Returns a tuple containing a slice of the full list
     starting at next_token and ending with at most the
@@ -754,7 +771,7 @@ def paginated_list(full_list, max_results, next_token):
     return sorted_list[start:end], new_next
 
 
-def validate_safe_to_delete(cluster):
+def validate_safe_to_delete(cluster: Cluster) -> None:
     # A cluster which has nodegroups attached can not be deleted.
     if cluster.nodegroup_count:
         nodegroup_names = ",".join(list(cluster.nodegroups.keys()))
@@ -766,7 +783,9 @@ def validate_safe_to_delete(cluster):
         )
 
 
-def validate_launch_template_combination(disk_size, remote_access):
+def validate_launch_template_combination(
+    disk_size: Optional[int], remote_access: Optional[Dict[str, Any]]
+) -> None:
     if not (disk_size or remote_access):
         return
 
@@ -777,8 +796,8 @@ def validate_launch_template_combination(disk_size, remote_access):
     )
 
 
-def _validate_fargate_profile_selectors(selectors):
-    def raise_exception(message):
+def _validate_fargate_profile_selectors(selectors: List[Dict[str, Any]]) -> None:
+    def raise_exception(message: str) -> None:
         raise InvalidParameterException(
             clusterName=None,
             nodegroupName=None,
diff --git a/moto/eks/responses.py b/moto/eks/responses.py
index c0deb4845..b87054eab 100644
--- a/moto/eks/responses.py
+++ b/moto/eks/responses.py
@@ -1,22 +1,24 @@
 import json
+from typing import Any
 from urllib.parse import unquote
+from moto.core.common_types import TYPE_RESPONSE
 from moto.core.responses import BaseResponse
 
-from .models import eks_backends
+from .models import eks_backends, EKSBackend
 
 DEFAULT_MAX_RESULTS = 100
 DEFAULT_NEXT_TOKEN = ""
 
 
 class EKSResponse(BaseResponse):
-    def __init__(self):
+    def __init__(self) -> None:
         super().__init__(service_name="eks")
 
     @property
-    def eks_backend(self):
+    def eks_backend(self) -> EKSBackend:
         return eks_backends[self.current_account][self.region]
 
-    def create_cluster(self):
+    def create_cluster(self) -> TYPE_RESPONSE:
         name = self._get_param("name")
         version = self._get_param("version")
         role_arn = self._get_param("roleArn")
@@ -41,7 +43,7 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps({"cluster": dict(cluster)})
 
-    def create_fargate_profile(self):
+    def create_fargate_profile(self) -> TYPE_RESPONSE:
         fargate_profile_name = self._get_param("fargateProfileName")
         cluster_name = self._get_param("name")
         pod_execution_role_arn = self._get_param("podExecutionRoleArn")
@@ -62,7 +64,7 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps({"fargateProfile": dict(fargate_profile)})
 
-    def create_nodegroup(self):
+    def create_nodegroup(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         nodegroup_name = self._get_param("nodegroupName")
         scaling_config = self._get_param("scalingConfig")
@@ -101,14 +103,14 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps({"nodegroup": dict(nodegroup)})
 
-    def describe_cluster(self):
+    def describe_cluster(self) -> TYPE_RESPONSE:
         name = self._get_param("name")
 
         cluster = self.eks_backend.describe_cluster(name=name)
 
         return 200, {}, json.dumps({"cluster": dict(cluster)})
 
-    def describe_fargate_profile(self):
+    def describe_fargate_profile(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         fargate_profile_name = self._get_param("fargateProfileName")
 
@@ -117,7 +119,7 @@ class EKSResponse(BaseResponse):
         )
         return 200, {}, json.dumps({"fargateProfile": dict(fargate_profile)})
 
-    def describe_nodegroup(self):
+    def describe_nodegroup(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         nodegroup_name = self._get_param("nodegroupName")
 
@@ -127,7 +129,7 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps({"nodegroup": dict(nodegroup)})
 
-    def list_clusters(self):
+    def list_clusters(self) -> TYPE_RESPONSE:
         max_results = self._get_int_param("maxResults", DEFAULT_MAX_RESULTS)
         next_token = self._get_param("nextToken", DEFAULT_NEXT_TOKEN)
 
@@ -137,7 +139,7 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps(dict(clusters=clusters, nextToken=next_token))
 
-    def list_fargate_profiles(self):
+    def list_fargate_profiles(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         max_results = self._get_int_param("maxResults", DEFAULT_MAX_RESULTS)
         next_token = self._get_param("nextToken", DEFAULT_NEXT_TOKEN)
@@ -154,7 +156,7 @@ class EKSResponse(BaseResponse):
             ),
         )
 
-    def list_nodegroups(self):
+    def list_nodegroups(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         max_results = self._get_int_param("maxResults", DEFAULT_MAX_RESULTS)
         next_token = self._get_param("nextToken", DEFAULT_NEXT_TOKEN)
@@ -165,14 +167,14 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps(dict(nodegroups=nodegroups, nextToken=next_token))
 
-    def delete_cluster(self):
+    def delete_cluster(self) -> TYPE_RESPONSE:
         name = self._get_param("name")
 
         cluster = self.eks_backend.delete_cluster(name=name)
 
         return 200, {}, json.dumps({"cluster": dict(cluster)})
 
-    def delete_fargate_profile(self):
+    def delete_fargate_profile(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         fargate_profile_name = self._get_param("fargateProfileName")
 
@@ -182,7 +184,7 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps({"fargateProfile": dict(fargate_profile)})
 
-    def delete_nodegroup(self):
+    def delete_nodegroup(self) -> TYPE_RESPONSE:
         cluster_name = self._get_param("name")
         nodegroup_name = self._get_param("nodegroupName")
 
@@ -192,7 +194,7 @@ class EKSResponse(BaseResponse):
 
         return 200, {}, json.dumps({"nodegroup": dict(nodegroup)})
 
-    def tags(self, request, full_url, headers):
+    def tags(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:  # type: ignore[return]
         self.setup_class(request, full_url, headers)
         if request.method == "GET":
             return self.list_tags_for_resource()
@@ -201,25 +203,25 @@ class EKSResponse(BaseResponse):
         if request.method == "DELETE":
             return self.untag_resource()
 
-    def tag_resource(self):
+    def tag_resource(self) -> TYPE_RESPONSE:
         self.eks_backend.tag_resource(
             self._extract_arn_from_path(), self._get_param("tags")
         )
 
         return 200, {}, ""
 
-    def untag_resource(self):
+    def untag_resource(self) -> TYPE_RESPONSE:
         self.eks_backend.untag_resource(
             self._extract_arn_from_path(), self._get_param("tagKeys")
         )
 
         return 200, {}, ""
 
-    def list_tags_for_resource(self):
+    def list_tags_for_resource(self) -> TYPE_RESPONSE:
         tags = self.eks_backend.list_tags_for_resource(self._extract_arn_from_path())
         return 200, {}, json.dumps({"tags": tags})
 
-    def _extract_arn_from_path(self):
+    def _extract_arn_from_path(self) -> str:
         # /tags/arn_that_may_contain_a_slash
         path = unquote(self.path)
         return "/".join(path.split("/")[2:])
diff --git a/moto/eks/utils.py b/moto/eks/utils.py
index 686cd2aad..d7722dbc7 100644
--- a/moto/eks/utils.py
+++ b/moto/eks/utils.py
@@ -6,7 +6,7 @@ from boto3 import Session
 from moto.eks.exceptions import InvalidParameterException
 
 
-def get_partition(region):
+def get_partition(region: str) -> str:
     valid_matches = [
         # (region prefix, aws partition)
         ("cn-", "aws-cn"),
@@ -21,7 +21,7 @@ def get_partition(region):
     return "aws"
 
 
-def method_name(use_parent=False):
+def method_name(use_parent: bool = False) -> str:
     """
     Returns the name of the method which called it from the stack in PascalCase.
     If `use_parent` is True, returns the parent of the method which called it instead.
@@ -37,12 +37,12 @@ def method_name(use_parent=False):
     )
 
 
-def validate_role_arn(arn):
+def validate_role_arn(arn: str) -> None:
     valid_role_arn_format = re.compile(
         "arn:(?P<partition>.+):iam::(?P<account_id>[0-9]{12}):role/.+"
     )
     match = valid_role_arn_format.match(arn)
-    valid_partition = match.group("partition") in Session().get_available_partitions()
+    valid_partition = match.group("partition") in Session().get_available_partitions()  # type: ignore
 
     if not all({arn, match, valid_partition}):
-        raise InvalidParameterException("Invalid Role Arn: '" + arn + "'")
+        raise InvalidParameterException("Invalid Role Arn: '" + arn + "'")  # type: ignore
diff --git a/setup.cfg b/setup.cfg
index de3c1ad34..228066350 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -229,7 +229,7 @@ disable = W,C,R,E
 enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import
 
 [mypy]
-files= moto/a*,moto/b*,moto/c*,moto/d*,moto/ebs/,moto/ec2,moto/ec2instanceconnect,moto/ecr,moto/ecs,moto/efs,moto/es,moto/moto_api
+files= moto/a*,moto/b*,moto/c*,moto/d*,moto/ebs/,moto/ec2,moto/ec2instanceconnect,moto/ecr,moto/ecs,moto/efs,moto/eks,moto/es,moto/moto_api
 show_column_numbers=True
 show_error_codes = True
 disable_error_code=abstract


Test patch:
diff --git a/tests/test_core/test_moto_api.py b/tests/test_core/test_moto_api.py
index 3d69ba23b..cac92f034 100644
--- a/tests/test_core/test_moto_api.py
+++ b/tests/test_core/test_moto_api.py
@@ -5,8 +5,9 @@ import boto3
 import json
 import pytest
 from botocore.exceptions import ClientError
-from moto import mock_autoscaling, mock_sqs, settings
-from unittest import SkipTest
+from moto import mock_autoscaling, mock_s3, mock_sqs, settings
+from moto.core.model_instances import model_data, reset_model_data
+from unittest import SkipTest, TestCase
 
 base_url = (
     "http://localhost:5000"
@@ -73,3 +74,61 @@ def test_creation_error__data_api_still_returns_thing():
 
     names = [obj["name"] for obj in as_objects["FakeAutoScalingGroup"]]
     names.should.contain("test_asg")
+
+
+def test_model_data_is_emptied_as_necessary():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("We're only interested in the decorator performance here")
+
+    # Reset any residual data
+    reset_model_data()
+
+    # No instances exist, because we have just reset it
+    for classes_per_service in model_data.values():
+        for _class in classes_per_service.values():
+            _class.instances.should.equal([])
+
+    with mock_sqs():
+        # When just starting a mock, it is empty
+        for classes_per_service in model_data.values():
+            for _class in classes_per_service.values():
+                _class.instances.should.equal([])
+
+        # After creating a queue, some data will be present
+        conn = boto3.client("sqs", region_name="us-west-1")
+        conn.create_queue(QueueName="queue1")
+
+        model_data["sqs"]["Queue"].instances.should.have.length_of(1)
+
+    # But after the mock ends, it is empty again
+    for classes_per_service in model_data.values():
+        for _class in classes_per_service.values():
+            _class.instances.should.equal([])
+
+    # When we have multiple/nested mocks, the data should still be present after the first mock ends
+    with mock_sqs():
+        conn = boto3.client("sqs", region_name="us-west-1")
+        conn.create_queue(QueueName="queue1")
+        with mock_s3():
+            # The data should still be here - instances should not reset if another mock is still active
+            model_data["sqs"]["Queue"].instances.should.have.length_of(1)
+        # The data should still be here - the inner mock has exited, but the outer mock is still active
+        model_data["sqs"]["Queue"].instances.should.have.length_of(1)
+
+
+@mock_sqs
+class TestModelDataResetForClassDecorator(TestCase):
+    def setUp(self):
+        if settings.TEST_SERVER_MODE:
+            raise SkipTest("We're only interested in the decorator performance here")
+
+        # No data is present at the beginning
+        for classes_per_service in model_data.values():
+            for _class in classes_per_service.values():
+                _class.instances.should.equal([])
+
+        conn = boto3.client("sqs", region_name="us-west-1")
+        conn.create_queue(QueueName="queue1")
+
+    def test_should_find_bucket(self):
+        model_data["sqs"]["Queue"].instances.should.have.length_of(1)
diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py
index 8f3459ef9..2146184dc 100644
--- a/tests/test_dynamodb/test_dynamodb.py
+++ b/tests/test_dynamodb/test_dynamodb.py
@@ -4708,6 +4708,17 @@ def test_gsi_projection_type_include():
         }
     )
 
+    # Same when scanning the table
+    items = table.scan(IndexName="GSI-INC")["Items"]
+    items[0].should.equal(
+        {
+            "gsiK1PartitionKey": "gsi-pk",
+            "gsiK1SortKey": "gsi-sk",
+            "partitionKey": "pk-1",
+            "projectedAttribute": "lore ipsum",
+        }
+    )
+
 
 @mock_dynamodb
 def test_lsi_projection_type_keys_only():
@@ -4756,6 +4767,12 @@ def test_lsi_projection_type_keys_only():
         {"partitionKey": "pk-1", "sortKey": "sk-1", "lsiK1SortKey": "lsi-sk"}
     )
 
+    # Same when scanning the table
+    items = table.scan(IndexName="LSI")["Items"]
+    items[0].should.equal(
+        {"lsiK1SortKey": "lsi-sk", "partitionKey": "pk-1", "sortKey": "sk-1"}
+    )
+
 
 @mock_dynamodb
 @pytest.mark.parametrize(
diff --git a/tests/test_s3/test_s3_file_handles.py b/tests/test_s3/test_s3_file_handles.py
index cb4366500..70602145d 100644
--- a/tests/test_s3/test_s3_file_handles.py
+++ b/tests/test_s3/test_s3_file_handles.py
@@ -168,6 +168,7 @@ class TestS3FileHandleClosures(TestCase):
     @verify_zero_warnings
     def test_overwrite_versioned_upload(self):
         self.s3.put_object("versioned-bucket", "my-key", "x" * 10_000_000)
+        self.s3.put_object("versioned-bucket", "my-key", "x" * 10_000_000)
 
     @verify_zero_warnings
     def test_multiple_versions_upload(self):
@@ -262,6 +263,21 @@ class TestS3FileHandleClosuresUsingMocks(TestCase):
             version = self.s3.put_object(Bucket="foo", Key="b", Body="s")["VersionId"]
             self.s3.delete_object(Bucket="foo", Key="b", VersionId=version)
 
+    @verify_zero_warnings
+    def test_update_versioned_object__while_looping(self):
+        for _ in (1, 2):
+            with mock_s3():
+                self.s3.create_bucket(Bucket="foo")
+                self.s3.put_bucket_versioning(
+                    Bucket="foo",
+                    VersioningConfiguration={
+                        "Status": "Enabled",
+                        "MFADelete": "Disabled",
+                    },
+                )
+                self.s3.put_object(Bucket="foo", Key="bar", Body="stuff")
+                self.s3.put_object(Bucket="foo", Key="bar", Body="stuff2")
+
 
 def test_verify_key_can_be_copied_after_disposing():
     # https://github.com/getmoto/moto/issues/5588