Skip to content

Commit

Permalink
Simplify custom serializer interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Suor committed Apr 30, 2021
1 parent 04e526a commit a5ed1ac
Show file tree
Hide file tree
Showing 9 changed files with 30 additions and 56 deletions.
43 changes: 10 additions & 33 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ Setup redis connection and enable caching for desired models:
# should be compatible or subclass cacheops.redis.CacheopsRedis
CACHEOPS_CLIENT_CLASS = 'your.redis.ClientClass'
# To use your own serializer class,
# should be compatible or subclass cacheops.serializers.PickleSerializer
CACHEOPS_SERIALIZER = 'your.serializers.ClientClass'
CACHEOPS = {
# Automatically cache any User.objects.get() calls for 15 minutes
# This also includes .first() and .last() calls,
Expand Down Expand Up @@ -666,44 +662,25 @@ A ``query`` object passed to callback also enables reflection on used databases
**NOTE:** prefix is not used in simple and file cache. This might change in future cacheops.


Pickle serializer
-----------------
Custom serialization
--------------------

Cacheops by default using serializer build on ``pickle`` lib:
Cacheops uses ``pickle`` by default, employing it's default protocol. But you can specify your own
it might be any module or a class having `.dumps()` and `.loads()` functions. For example you can use ``dill`` instead, which can serialize more things like anonymous functions:

.. code:: python
import pickle
class PickleSerializer:
# properties
PickleError = pickle.PickleError
HIGHEST_PROTOCOL = pickle.HIGHEST_PROTOCOL
# methods
dumps = pickle.dumps
loads = pickle.loads
You can overwrite it to any own pickle serializer in two step (for ``dill`` lib):
CACHEOPS_SERIALIZER = 'dill'
* setup in settings ``CACHEOPS_SERIALIZER = 'your.serializers.DillSerializer'``
* write own serializer like this:
One less obvious use is to fix pickle protocol, to use cacheops cache across python versions:

.. code:: python
import dill
class DillSerializer:
# properties
PickleError = dill.PicklingError
HIGHEST_PROTOCOL = dill.HIGHEST_PROTOCOL
# methods
dumps = dill.dumps
loads = dill.loads
import pickle
**NOTE:** this can be helpful on raises like: "AttributeError: Can't pickle local object 'curry.<locals>._curried'".
class CACHEOPS_SERIALIZER:
dumps = lambda data: pickle.dumps(data, 3)
loads = pickle.loads
Using memory limit
Expand Down
14 changes: 11 additions & 3 deletions cacheops/conf.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from importlib import import_module
from funcy import memoize, merge

from django.conf import settings as base_settings
from django.core.exceptions import ImproperlyConfigured
from django.core.signals import setting_changed
from django.utils.module_loading import import_string


ALL_OPS = {'get', 'fetch', 'count', 'aggregate', 'exists'}
Expand All @@ -23,7 +23,7 @@ class Defaults:
# and one should not filter by their equality anyway.
CACHEOPS_SKIP_FIELDS = "FileField", "TextField", "BinaryField", "JSONField"
CACHEOPS_LONG_DISJUNCTION = 8
CACHEOPS_SERIALIZER = 'cacheops.serializers.PickleSerializer'
CACHEOPS_SERIALIZER = 'pickle'

FILE_CACHE_DIR = '/tmp/cacheops_file_cache'
FILE_CACHE_TIMEOUT = 60*60*24*30
Expand All @@ -33,7 +33,7 @@ class Settings(object):
def __getattr__(self, name):
res = getattr(base_settings, name, getattr(Defaults, name))
if name in ['CACHEOPS_PREFIX', 'CACHEOPS_SERIALIZER']:
res = res if callable(res) else import_string(res)
res = import_string(res) if isinstance(res, str) else res

# Convert old list of classes to list of strings
if name == 'CACHEOPS_SKIP_FIELDS':
Expand All @@ -47,6 +47,14 @@ def __getattr__(self, name):
setting_changed.connect(lambda setting, **kw: settings.__dict__.pop(setting, None), weak=False)


def import_string(path):
if "." in path:
module, attr = path.rsplit(".", 1)
return getattr(import_module(module), attr)
else:
return import_module(path)


@memoize
def prepare_profiles():
"""
Expand Down
2 changes: 1 addition & 1 deletion cacheops/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def cache_thing(prefix, cache_key, data, cond_dnfs, timeout, dbs=(), precall_key
load_script('cache_thing', settings.CACHEOPS_LRU)(
keys=[prefix, cache_key, precall_key],
args=[
settings.CACHEOPS_SERIALIZER.dumps(data, -1),
settings.CACHEOPS_SERIALIZER.dumps(data),
json.dumps(cond_dnfs, default=str),
timeout
]
Expand Down
7 changes: 3 additions & 4 deletions cacheops/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def get(self, cache_key):

@handle_connection_failure
def set(self, cache_key, data, timeout=None):
pickled_data = settings.CACHEOPS_SERIALIZER.dumps(data, -1)
pickled_data = settings.CACHEOPS_SERIALIZER.dumps(data)
if timeout is not None:
self.conn.setex(cache_key, timeout, pickled_data)
else:
Expand Down Expand Up @@ -133,7 +133,7 @@ def get(self, key):

with open(filename, 'rb') as f:
return settings.CACHEOPS_SERIALIZER.load(f)
except (IOError, OSError, EOFError, settings.CACHEOPS_SERIALIZER.PickleError):
except (IOError, OSError, EOFError):
raise CacheMiss

def set(self, key, data, timeout=None):
Expand All @@ -150,8 +150,7 @@ def set(self, key, data, timeout=None):
# Use open with exclusive rights to prevent data corruption
f = os.open(filename, os.O_EXCL | os.O_WRONLY | os.O_CREAT)
try:
os.write(f, settings.CACHEOPS_SERIALIZER.dumps(
data, settings.CACHEOPS_SERIALIZER.HIGHEST_PROTOCOL))
os.write(f, settings.CACHEOPS_SERIALIZER.dumps(data))
finally:
os.close(f)

Expand Down
1 change: 1 addition & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ funcy>=1.8,<2.0
six>=1.4.0
before_after==1.0.0
jinja2>=2.10
dill
4 changes: 2 additions & 2 deletions tests/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@


posts = list(Post.objects.cache().all())
posts_pickle = settings.CACHEOPS_SERIALIZER.dumps(posts, -1)
posts_pickle = settings.CACHEOPS_SERIALIZER.dumps(posts)

def do_pickle():
settings.CACHEOPS_SERIALIZER.dumps(posts, -1)
settings.CACHEOPS_SERIALIZER.dumps(posts)

def do_unpickle():
settings.CACHEOPS_SERIALIZER.loads(posts_pickle)
Expand Down
12 changes: 0 additions & 12 deletions tests/serializers.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ def test_385(self):

invalidate_model(Client)

with override_settings(CACHEOPS_SERIALIZER='tests.serializers.DillSerializer'):
with override_settings(CACHEOPS_SERIALIZER='dill'):
with self.assertNumQueries(1):
Client.objects.filter(name='Client Name').cache().first()
Client.objects.filter(name='Client Name').cache().first()
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ deps =
pypy3: psycopg2cffi>=2.7.6
before_after==1.0.0
jinja2>=2.10
dill
commands =
./run_tests.py []
env CACHEOPS_PREFIX=1 ./run_tests.py []
Expand Down

0 comments on commit a5ed1ac

Please sign in to comment.