Skip to content

Commit

Permalink
Cache serialized version of Attribute, handle cache failures, legibil…
Browse files Browse the repository at this point in the history
…ity (#20)
  • Loading branch information
alukach authored and oliverroick committed Jul 14, 2017
1 parent 76bdf43 commit 9e7ee0a
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 4 deletions.
2 changes: 1 addition & 1 deletion jsonattrs/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.1.22'
__version__ = '0.1.23'
23 changes: 20 additions & 3 deletions jsonattrs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,11 @@ def save(self, *args, **kwargs):
def compose_schemas(*schemas):
key = 'jsonattrs:compose:' + ','.join([str(s.pk) for s in schemas])
cached = caches['jsonattrs'].get(key)
if cached is not None:
return cached
if cached:
s_attrs, required_attrs, default_attrs = cached
# Deserialize attrs when retrieving from cache
attrs = OrderedDict((k, Attribute(**v)) for k, v in s_attrs.items())
return attrs, required_attrs, default_attrs

# Extract schema attributes, names of required attributes and
# names of attributes with defaults, composing schemas.
Expand All @@ -123,7 +126,9 @@ def compose_schemas(*schemas):
default_attrs = {n for n, a in attrs.items()
if a.default is not None and a.default != ''}

caches['jsonattrs'].set(key, (attrs, required_attrs, default_attrs))
# Serialize attrs to make it smaller in cache
s_attrs = OrderedDict((k, v.to_dict()) for k, v in attrs.items())
caches['jsonattrs'].set(key, (s_attrs, required_attrs, default_attrs))
return attrs, required_attrs, default_attrs


Expand Down Expand Up @@ -251,6 +256,12 @@ class Meta:

objects = AttributeManager()

def __str__(self):
return "<Attribute #{0.id}: name={0.name}>".format(self)

def __repr__(self):
return str(self)

@property
def long_name(self):
if self.long_name_xlat is None or isinstance(self.long_name_xlat, str):
Expand Down Expand Up @@ -344,3 +355,9 @@ def render(self, val):
return ', '.join(result)
else:
return self.choice_dict.get(val, val)

def to_dict(self):
return {
k: v for k, v in self.__dict__.items()
if k in [f.attname for f in self._meta.fields]
}
101 changes: 101 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from collections import OrderedDict
from django.test import TestCase
from unittest.mock import patch, MagicMock

from jsonattrs.models import Schema, Attribute, AttributeType, compose_schemas
from .fixtures import create_fixtures


class ComposeSchemaTest(TestCase):
def setUp(self):
self.fixtures = create_fixtures(do_schemas=False, load_attr_types=True)
self.schema = Schema.objects.create(
content_type=self.fixtures['party_t'], selectors=()
)
self.attr_type = AttributeType.objects.get(name='select_one')

@patch('jsonattrs.models.caches')
def test_compose_schemas_cache_deserialize(self, mock_caches):
attr1, attr2 = Attribute.objects.bulk_create(Attribute(
id=i,
schema_id=self.schema.id,
name='testattr%s' % i,
long_name='Test attribute%s' % i,
index=i,
attr_type_id=self.attr_type.id,
required=bool(i % 2),
default=i**i if not bool(i % 2) else '') for i in range(1, 3)
)
cache_value = (
OrderedDict([
('testattr1', attr1.to_dict()),
('testattr2', attr2.to_dict())
]),
{'testattr1'},
{'testattr2'}
)
mock_cache = MagicMock(get=MagicMock(return_value=cache_value))
mock_caches.__getitem__.return_value = mock_cache

assert compose_schemas(self.schema) == (
OrderedDict([
('testattr1', attr1),
('testattr2', attr2)
]),
{'testattr1'},
{'testattr2'}
)
assert not mock_cache.set.called

@patch('jsonattrs.models.caches')
def test_compose_schemas_cache_serialize(self, mock_caches):
mock_cache = MagicMock(get=MagicMock(return_value=None))
mock_caches.__getitem__.return_value = mock_cache

attr1, attr2 = Attribute.objects.bulk_create(Attribute(
id=i,
schema_id=self.schema.id,
name='testattr%s' % i,
long_name='Test attribute%s' % i,
index=i,
attr_type_id=self.attr_type.id,
required=bool(i % 2),
default=i**i if not bool(i % 2) else '') for i in range(1, 3)
)
attr1.refresh_from_db()
attr2.refresh_from_db()

assert compose_schemas(self.schema) == (
OrderedDict([
('testattr1', attr1),
('testattr2', attr2)
]),
{'testattr1'},
{'testattr2'}
)
mock_cache.set.assert_called_once_with(
'jsonattrs:compose:{}'.format(self.schema.id),
(
OrderedDict([
('testattr1', attr1.to_dict()),
('testattr2', attr2.to_dict())
]),
{'testattr1'},
{'testattr2'}
)
)

def test_serialize_deserialize(self):
attr = Attribute.objects.create(
schema_id=self.schema.id,
name='testattr',
long_name='Test attribute',
index=1,
attr_type_id=self.attr_type.id,
)
assert Attribute(**attr.to_dict()) == attr

def test_str(self):
attr = Attribute(name='testattr', id=123)
assert str(attr) == '<Attribute #123: name=testattr>'
assert repr(attr) == '<Attribute #123: name=testattr>'

0 comments on commit 9e7ee0a

Please sign in to comment.