Core: Rework model_data to only clear on exit (#5920)
This commit is contained in:
parent
b8350f2801
commit
40a3a529f9
@ -1,18 +1,15 @@
|
|||||||
from boto3 import Session
|
from boto3 import Session
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from typing import Any, List, Dict, Optional, ClassVar, TypeVar, Iterator
|
from typing import Any, List, Dict, Optional, ClassVar, TypeVar, Iterator
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from moto.settings import allow_unknown_region
|
from moto.settings import allow_unknown_region
|
||||||
|
from .model_instances import model_data
|
||||||
from .utils import convert_regex_to_flask_path
|
from .utils import convert_regex_to_flask_path
|
||||||
|
|
||||||
|
|
||||||
model_data: Dict[str, Dict[str, object]] = defaultdict(dict)
|
|
||||||
|
|
||||||
|
|
||||||
class InstanceTrackerMeta(type):
|
class InstanceTrackerMeta(type):
|
||||||
def __new__(meta, name: str, bases: Any, dct: Dict[str, Any]) -> type:
|
def __new__(meta, name: str, bases: Any, dct: Dict[str, Any]) -> type:
|
||||||
cls = super(InstanceTrackerMeta, meta).__new__(meta, name, bases, dct)
|
cls = super(InstanceTrackerMeta, meta).__new__(meta, name, bases, dct)
|
||||||
@ -31,16 +28,9 @@ class BaseBackend:
|
|||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.account_id = account_id
|
self.account_id = account_id
|
||||||
|
|
||||||
def _reset_model_refs(self) -> None:
|
|
||||||
# Remove all references to the models stored
|
|
||||||
for models in model_data.values():
|
|
||||||
for model in models.values():
|
|
||||||
model.instances = [] # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
region_name = self.region_name
|
region_name = self.region_name
|
||||||
account_id = self.account_id
|
account_id = self.account_id
|
||||||
self._reset_model_refs()
|
|
||||||
self.__dict__ = {}
|
self.__dict__ = {}
|
||||||
self.__init__(region_name, account_id) # type: ignore[misc]
|
self.__init__(region_name, account_id) # type: ignore[misc]
|
||||||
|
|
||||||
|
15
moto/core/model_instances.py
Normal file
15
moto/core/model_instances.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
"""
|
||||||
|
Storage of all instances that extend BaseModel
|
||||||
|
This allows us to easily expose all internal state using the MotoServer dashboard
|
||||||
|
"""
|
||||||
|
model_data: Dict[str, Dict[str, object]] = defaultdict(dict)
|
||||||
|
|
||||||
|
|
||||||
|
def reset_model_data() -> None:
|
||||||
|
# Remove all references to the models stored
|
||||||
|
for models in model_data.values():
|
||||||
|
for model in models.values():
|
||||||
|
model.instances = [] # type: ignore[attr-defined]
|
@ -23,6 +23,7 @@ from .custom_responses_mock import (
|
|||||||
not_implemented_callback,
|
not_implemented_callback,
|
||||||
reset_responses_mock,
|
reset_responses_mock,
|
||||||
)
|
)
|
||||||
|
from .model_instances import reset_model_data
|
||||||
|
|
||||||
DEFAULT_ACCOUNT_ID = "123456789012"
|
DEFAULT_ACCOUNT_ID = "123456789012"
|
||||||
CALLABLE_RETURN = TypeVar("CALLABLE_RETURN")
|
CALLABLE_RETURN = TypeVar("CALLABLE_RETURN")
|
||||||
@ -104,6 +105,7 @@ class BaseMockAWS:
|
|||||||
pass
|
pass
|
||||||
self.unmock_env_variables()
|
self.unmock_env_variables()
|
||||||
self.__class__.mocks_active = False
|
self.__class__.mocks_active = False
|
||||||
|
reset_model_data()
|
||||||
self.disable_patching() # type: ignore[attr-defined]
|
self.disable_patching() # type: ignore[attr-defined]
|
||||||
|
|
||||||
def decorate_callable(
|
def decorate_callable(
|
||||||
|
@ -1693,7 +1693,6 @@ class IAMBackend(BaseBackend):
|
|||||||
account_id = self.account_id
|
account_id = self.account_id
|
||||||
# Do not reset these policies, as they take a long time to load
|
# Do not reset these policies, as they take a long time to load
|
||||||
aws_policies = self.aws_managed_policies
|
aws_policies = self.aws_managed_policies
|
||||||
self._reset_model_refs()
|
|
||||||
self.__dict__ = {}
|
self.__dict__ = {}
|
||||||
self.__init__(region_name, account_id, aws_policies)
|
self.__init__(region_name, account_id, aws_policies)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from moto.core import BaseBackend, DEFAULT_ACCOUNT_ID
|
from moto.core import BaseBackend, DEFAULT_ACCOUNT_ID
|
||||||
|
from moto.core.model_instances import reset_model_data
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ class MotoAPIBackend(BaseBackend):
|
|||||||
continue
|
continue
|
||||||
for backend in backends_.values():
|
for backend in backends_.values():
|
||||||
backend.reset()
|
backend.reset()
|
||||||
|
reset_model_data()
|
||||||
self.__init__(region_name, account_id) # type: ignore[misc]
|
self.__init__(region_name, account_id) # type: ignore[misc]
|
||||||
|
|
||||||
def get_transition(self, model_name: str) -> Dict[str, Any]:
|
def get_transition(self, model_name: str) -> Dict[str, Any]:
|
||||||
|
@ -9,7 +9,7 @@ from typing import Any, Dict, List
|
|||||||
class MotoAPIResponse(BaseResponse):
|
class MotoAPIResponse(BaseResponse):
|
||||||
def reset_response(
|
def reset_response(
|
||||||
self,
|
self,
|
||||||
request: Any, # pylint: disable=unused-argument
|
request: Any,
|
||||||
full_url: str, # pylint: disable=unused-argument
|
full_url: str, # pylint: disable=unused-argument
|
||||||
headers: Any, # pylint: disable=unused-argument
|
headers: Any, # pylint: disable=unused-argument
|
||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
@ -22,7 +22,7 @@ class MotoAPIResponse(BaseResponse):
|
|||||||
|
|
||||||
def reset_auth_response(
|
def reset_auth_response(
|
||||||
self,
|
self,
|
||||||
request: Any, # pylint: disable=unused-argument
|
request: Any,
|
||||||
full_url: str, # pylint: disable=unused-argument
|
full_url: str, # pylint: disable=unused-argument
|
||||||
headers: Any, # pylint: disable=unused-argument
|
headers: Any, # pylint: disable=unused-argument
|
||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
@ -52,7 +52,7 @@ class MotoAPIResponse(BaseResponse):
|
|||||||
full_url: str, # pylint: disable=unused-argument
|
full_url: str, # pylint: disable=unused-argument
|
||||||
headers: Any, # pylint: disable=unused-argument
|
headers: Any, # pylint: disable=unused-argument
|
||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
from moto.core.base_backend import model_data
|
from moto.core.model_instances import model_data
|
||||||
|
|
||||||
results: Dict[str, Dict[str, List[Any]]] = {}
|
results: Dict[str, Dict[str, List[Any]]] = {}
|
||||||
for service in sorted(model_data):
|
for service in sorted(model_data):
|
||||||
@ -86,7 +86,7 @@ class MotoAPIResponse(BaseResponse):
|
|||||||
|
|
||||||
def get_transition(
|
def get_transition(
|
||||||
self,
|
self,
|
||||||
request: Any, # pylint: disable=unused-argument
|
request: Any,
|
||||||
full_url: str, # pylint: disable=unused-argument
|
full_url: str, # pylint: disable=unused-argument
|
||||||
headers: Any, # pylint: disable=unused-argument
|
headers: Any, # pylint: disable=unused-argument
|
||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
@ -103,9 +103,9 @@ class MotoAPIResponse(BaseResponse):
|
|||||||
|
|
||||||
def set_transition(
|
def set_transition(
|
||||||
self,
|
self,
|
||||||
request: Any, # pylint: disable=unused-argument
|
request: Any,
|
||||||
full_url: str, # pylint: disable=unused-argument
|
full_url: str, # pylint: disable=unused-argument
|
||||||
headers: Any, # pylint: disable=unused-argument
|
headers: Any,
|
||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
from .models import moto_api_backend
|
from .models import moto_api_backend
|
||||||
|
|
||||||
@ -120,9 +120,9 @@ class MotoAPIResponse(BaseResponse):
|
|||||||
|
|
||||||
def unset_transition(
|
def unset_transition(
|
||||||
self,
|
self,
|
||||||
request: Any, # pylint: disable=unused-argument
|
request: Any,
|
||||||
full_url: str, # pylint: disable=unused-argument
|
full_url: str, # pylint: disable=unused-argument
|
||||||
headers: Any, # pylint: disable=unused-argument
|
headers: Any,
|
||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
from .models import moto_api_backend
|
from .models import moto_api_backend
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import gc
|
|||||||
import warnings
|
import warnings
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from moto import settings
|
from moto import settings
|
||||||
|
from moto.dynamodb.models import DynamoDBBackend
|
||||||
from moto.s3 import models as s3model
|
from moto.s3 import models as s3model
|
||||||
from moto.s3.responses import S3ResponseInstance
|
from moto.s3.responses import S3ResponseInstance
|
||||||
from unittest import SkipTest, TestCase
|
from unittest import SkipTest, TestCase
|
||||||
@ -188,6 +189,13 @@ class TestS3FileHandleClosures(TestCase):
|
|||||||
bucket_name="my-bucket", key_name="my-key", version_id=key._version_id
|
bucket_name="my-bucket", key_name="my-key", version_id=key._version_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@verify_zero_warnings
|
||||||
|
def test_reset_other_backend(self):
|
||||||
|
db = DynamoDBBackend("us-west-1", "1234")
|
||||||
|
# This used to empty the entire list of `model_instances`, which can contain FakeKey-references
|
||||||
|
# Verify that we can reset an unrelated backend, without throwing away FakeKey-references that still need to be disposed
|
||||||
|
db.reset()
|
||||||
|
|
||||||
|
|
||||||
def test_verify_key_can_be_copied_after_disposing():
|
def test_verify_key_can_be_copied_after_disposing():
|
||||||
# https://github.com/getmoto/moto/issues/5588
|
# https://github.com/getmoto/moto/issues/5588
|
||||||
|
Loading…
Reference in New Issue
Block a user