Skip to content
Closed
4 changes: 0 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,8 @@ matrix:
include:
- python: "3.5"
env: TOX_ENV=flake8
- python: "2.6"
env: TOX_ENV=py26
- python: "2.7"
env: TOX_ENV=py27
- python: "3.3"
env: TOX_ENV=py33
- python: "3.4"
env: TOX_ENV=py34
- python: "3.5"
Expand Down
84 changes: 31 additions & 53 deletions premailer/cache.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,61 @@
import functools


class _HashedSeq(list):
# # From CPython
__slots__ = 'hashvalue'

def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)

def __hash__(self):
return self.hashvalue


# if we only have nonlocal
class _Cache(object):
def __init__(self):
self.off = False
self.missed = 0
self.cache = {}


def function_cache(expected_max_entries=1000):
def function_cache():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now is not the time but the correct name ought to be "function_memoize" or something like that.

"""
function_cache is a decorator for caching function call
the argument to the wrapped function must be hashable else
function_cache is a decorator for caching function call.
The argument to the wrapped function must be hashable else
it will not work
expected_max_entries is for protecting cache failure. If cache
misses more than this number the cache will turn off itself.
Specify None you sure that the cache will not cause memory
limit problem.
Args:
expected_max_entries(integer OR None): will raise if not correct
Function wrapper accepts max_cache_entries argument which specifies
the maximum number of different data to cache. Specifying None will not
limit the size of the cache at all.
Returns:
function
"""
if (
expected_max_entries is not None and
not isinstance(expected_max_entries, int)
):
raise TypeError(
'Expected expected_max_entries to be an integer or None'
)

# indicator of cache missed
sentinel = object()

def decorator(func):
cached = _Cache()
cache = {}

@functools.wraps(func)
def inner(*args, **kwargs):
if cached.off:
max_cache_entries = kwargs.pop('max_cache_entries', 1000)

if (
max_cache_entries is not None and
not isinstance(max_cache_entries, int)
):
raise TypeError(
'Expected max_cache_entries to be an integer or None'
)

if max_cache_entries == 0:
return func(*args, **kwargs)

keys = args
keys = []
for arg in args:
if isinstance(arg, list):
keys.append(tuple(arg))
else:
keys.append(arg)

if kwargs:
sorted_items = sorted(kwargs.items())
for item in sorted_items:
keys += item
keys.append(item)

hashed = hash(_HashedSeq(keys))
result = cached.cache.get(hashed, sentinel)
hashed = hash(tuple(keys))
result = cache.get(hashed, sentinel)
if result is sentinel:
cached.missed += 1
result = func(*args, **kwargs)
cached.cache[hashed] = result
# # something is wrong if we are here more than expected
# # empty and turn it off
if (
expected_max_entries is not None and
cached.missed > expected_max_entries
):
cached.off = True
cached.cache.clear()

if max_cache_entries is None or len(cache) < max_cache_entries:
cache[hashed] = result
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit strange that the caching just decides to stubbornly refuse to add more entries. I've already forgotten what you said in the justification in the PR description. Can you please add a code comment here to explain this reasoning.


return result

Expand Down
3 changes: 3 additions & 0 deletions premailer/merge_style.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import absolute_import
import cssutils
import threading
from premailer.cache import function_cache
from operator import itemgetter
try:
from collections import OrderedDict
Expand All @@ -15,6 +17,7 @@ def format_value(prop):
return prop.propertyValue.cssText.strip()


@function_cache()
def csstext_to_pairs(csstext):
"""
csstext_to_pairs takes css text and make it to list of
Expand Down
Loading