Skip to content

Commit

Permalink
Fix a memory leak caused by schematics json schemas
Browse files Browse the repository at this point in the history
Schematics types are instantiated before being used as a key to
identify whether the json schema has already been extracted. As
a result, every time the schema is extracted, the previous cached
value is not used, causing the cache dict to grow indefinitely.
  • Loading branch information
toumorokoshi committed Dec 19, 2019
1 parent d69ad41 commit 9db64a9
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# uranium
.virtualenv
.env
bin
build
Expand Down
15 changes: 10 additions & 5 deletions transmute_core/object_serializers/schematics_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ def _translate_to_model(self, model):
if isinstance(model, BaseType):
return model

if model in self._models:
return self._models[model]

if isinstance(model, ModelMeta):
return ModelType(model)
self._models[model] = ModelType(model)

if model not in self._models and isinstance(model, tuple):
self._models[model] = ListType(self._translate_to_model(model[0]))

if model in self._models:
return self._models[model]

return model
return self._models.get(model, model)

@staticmethod
def _to_key(model):
Expand Down Expand Up @@ -118,7 +118,12 @@ def dump(self, model, value):
def to_json_schema(self, model):
if isinstance(model, Serializable):
model = model.type
# ensure that the model is an instance,
# as further processing steps required that it is.
model = _enforce_instance(model)
# once the model is an instance of something, there
# are several legacy transforms (like taking a tuple of schematics
# models) that require a second transformation to a pure schematics model.
model = self._translate_to_model(model)
return _to_json_schema(model)

Expand Down
28 changes: 26 additions & 2 deletions transmute_core/tests/test_schematics.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from schematics.types.compound import DictType, ModelType
from schematics.exceptions import ValidationError
from transmute_core.exceptions import SerializationException, NoSerializerFound
from transmute_core.object_serializers.schematics_serializer import SchematicsSerializer


class Card(Model):
Expand Down Expand Up @@ -85,8 +86,7 @@ def test_schematics_to_json_schema(object_serializer_set):
(IntType, {"type": "integer"}),
(datetime, {"type": "string", "format": "date-time"}),
(UTCDateTimeType, {"type": "string", "format": "date-time"}),
(Serializable(fget=lambda: None,
type=StringType()), {"type": "string"}),
(Serializable(fget=lambda: None, type=StringType()), {"type": "string"}),
],
)
def test_to_json_schema(object_serializer_set, inp, expected):
Expand Down Expand Up @@ -165,3 +165,27 @@ def test_can_handle(object_serializer_set, cls, should_handle):
the object serializer set should not raise an exception if it can handle these types.
"""
object_serializer_set[cls]


def test_schematics_uses_cached_entries():
"""
Regression test. Validating that SchematicsSerializer uses the
same json schema it used previously, as before erroneous
use of the instantiated model as a key was causing memory leaks.
"""
serializer = SchematicsSerializer()
# A nested schema type is required as primitives have
# hard-coded dictionaries representing the json.
class SchematicsBody(Model):
name = StringType(max_length=5)

# ensure that instances have the same key as well.
instance = ModelType(SchematicsBody)
original_payload = serializer.to_json_schema(instance)
second_payload = serializer.to_json_schema(instance)
assert original_payload is second_payload

# classes are also valid output to recieve that the json schema
original_payload = serializer.to_json_schema(SchematicsBody)
second_payload = serializer.to_json_schema(SchematicsBody)
assert original_payload is second_payload
4 changes: 2 additions & 2 deletions uranium
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ URANIUM_GITHUB_ACCOUNT = os.getenv("URANIUM_GITHUB_ACCOUNT", "toumorokoshi")
URANIUM_GITHUB_BRANCH = os.getenv("URANIUM_GITHUB_BRANCH", "master")
URANIUM_STANDALONE_URL = "https://raw.githubusercontent.com/{account}/uranium/{branch}/uranium/scripts/uranium_standalone".format(
account=URANIUM_GITHUB_ACCOUNT, branch=URANIUM_GITHUB_BRANCH
)
CACHE_DIRECTORY = os.path.join(ROOT, ".env", ".uranium")
virtual)
CACHE_DIRECTORY = os.path.join(ROOT, ".virtualenv", ".uranium")
CACHED_URANIUM_STANDALONE = os.path.join(CACHE_DIRECTORY, "uranium_standalone")
# set uranium to use to 2.0a0
os.environ["URANIUM_VERSION"] = "2.0a1"
Expand Down

0 comments on commit 9db64a9

Please sign in to comment.