Skip to content

Commit

Permalink
Ported cache system to Python 3.
Browse files Browse the repository at this point in the history
I changed the API for this; before, the filesystem cache tried to
write strings directly, and only pickled other types, and required
some help from the caller on get() to know whether to unpickle.

Apart from being confusing, this was also a bit of a mindfuck trying
to make it work both on Python 2 and 3 (which is not to say it wouldn't
have been possible).

I've changed the BaseCache API a bit, and the filesystem cache now
simply pickles everything.
  • Loading branch information
miracle2k committed Apr 2, 2013
1 parent 69571bc commit bafc777
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 26 deletions.
8 changes: 8 additions & 0 deletions docs/upgrading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ When upgrading from an older version, you might encounter some backwards
incompatibility. The ``webassets`` API is not stable yet.


Development Version
~~~~~~~~~~~~~~~~~~~

- The API of the BaseCache.get() method has changed. It no longer receives
a ``python`` keyword argument. This only affects you if you have
implemented a custom cache class.


In 0.8
~~~~~~

Expand Down
44 changes: 19 additions & 25 deletions src/webassets/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import os
from os import path
import six
from webassets.merge import BaseHunk
from webassets.filter import Filter, freezedicts
from webassets.utils import md5_constructor, pickle
Expand All @@ -36,7 +37,7 @@ def make_hashable(data):
return freezedicts(data)


def make_md5(data):
def make_md5(*data):
"""Make a md5 hash based on``data``.
Specifically, this knows about ``Hunk`` objects, and makes sure
Expand All @@ -59,11 +60,15 @@ def walk(obj):
for d in walk(k): yield d
for d in walk(obj[k]): yield d
elif isinstance(obj, BaseHunk):
yield obj.data()
yield obj.data().encode('utf-8')
elif isinstance(obj, Filter):
yield str(hash(obj))
elif isinstance(obj, (int, basestring)):
yield str(obj)
yield str(hash(obj)).encode('utf-8')
elif isinstance(obj, int):
yield str(obj).encode('utf-8')
elif isinstance(obj, six.text_type):
yield obj.encode('utf-8')
elif isinstance(obj, six.binary_type):
yield obj
else:
raise ValueError('Cannot MD5 type %s' % type(obj))
md5 = md5_constructor()
Expand All @@ -72,13 +77,6 @@ def walk(obj):
return md5.hexdigest()


def maybe_pickle(value):
"""Pickle the given value if it is not a string."""
if not isinstance(value, basestring):
return pickle.dumps(value)
return value


def safe_unpickle(string):
"""Unpickle the string, or return ``None`` if that fails."""
try:
Expand All @@ -101,12 +99,8 @@ class BaseCache(object):
One cache instance can only be used safely with a single Environment.
"""

def get(self, key, python=None):
def get(self, key):
"""Should return the cache contents, or False.
If ``python`` is set, the cache value will be unpickled before it is
returned. You need this when you passed a non-string value to
:meth:`set`..
"""
raise NotImplementedError()

Expand Down Expand Up @@ -138,7 +132,7 @@ def __eq__(self, other):
None == other or \
id(self) == id(other)

def get(self, key, python=None):
def get(self, key):
key = make_hashable(key)
return self.cache.get(key, None)

Expand All @@ -162,6 +156,8 @@ class FilesystemCache(BaseCache):
"""Uses a temporary directory on the disk.
"""

V = 2 # We have changed the cache format once

def __init__(self, directory):
self.directory = directory

Expand All @@ -173,8 +169,8 @@ def __eq__(self, other):
self.directory == other or \
id(self) == id(other)

def get(self, key, python=None):
filename = path.join(self.directory, '%s' % make_md5(key))
def get(self, key):
filename = path.join(self.directory, '%s' % make_md5(self.V, key))
if not path.exists(filename):
return None
f = open(filename, 'rb')
Expand All @@ -183,15 +179,13 @@ def get(self, key, python=None):
finally:
f.close()

if python:
return safe_unpickle(result)
return result
return safe_unpickle(result)

def set(self, key, data):
filename = path.join(self.directory, '%s' % make_md5(key))
filename = path.join(self.directory, '%s' % make_md5(self.V, key))
f = open(filename, 'wb')
try:
f.write(maybe_pickle(data))
f.write(pickle.dumps(data))
finally:
f.close()

Expand Down
2 changes: 1 addition & 1 deletion src/webassets/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def data(self):
# TODO: "expires" header could be supported
if self.env and self.env.cache:
headers = self.env.cache.get(
('url', 'headers', self.url), python=True)
('url', 'headers', self.url))
if headers:
etag, lmod = headers
if etag: request.add_header('If-None-Match', etag)
Expand Down

0 comments on commit bafc777

Please sign in to comment.