evonove / django-money-rates

Currency conversion for django money
BSD 3-Clause "New" or "Revised" License
90 stars 61 forks source link

Caching of rates lookup. #5

Open mikecodona opened 9 years ago

mikecodona commented 9 years ago

Currently at least 2 database queries are performed for each currency conversion. This means that using django-money-rates for some tasks becomes infeasible, for example converting a large number of money amounts into a canonical currency so that they can be sorted according to true value.

With the current setup where rates are updated via a cronjob I don't think there would be a particular disadvantage in caching the results for 60s however possibly a more robust solution would be to use a configurable rates source backend which could be over-ridden to allow someone to add whatever caching logic they require.

At the moment I'm simply monkey patching the relevant functions:

djmoney_rates.utils.get_rate = memoize(djmoney_rates.utils.get_rate)
djmoney_rates.utils.get_rate_source = memoize(djmoney_rates.utils.get_rate_source)

Let me know if you have any thoughts or questions.

Thanks, Mike

davidastephens commented 7 years ago

+1 to this. Mike - can you share the memoize function are you using?

Thanks, Dave

mikecodona commented 7 years ago

Hi Dave,

We our own version of memoize which uses the django cache framework with the django.core.cache.backends.locmem.LocMemCache backend which allows us to have a timeout, but django.utils.functional.memoize will work and should give you somewhere to start if you need similar functionality.

Regards, Mike

tgckpg commented 7 years ago

+1 Also, to this date Django have deprecated memoize since 1.7 and recommends to use lru_cache from functools

spookylukey commented 6 years ago

We are doing the same:

Our cache function, which uses the Django cache backend but is generic for all kinds of functions:

from django.core.cache import cache as _djcache
from hashlib import sha1

def cache_results(seconds=900):
    """
    Cache the result of a function call for the specified number of seconds,
    using Django's caching mechanism.

    Assumes that the function never returns None (as the cache returns None to
    indicate a miss), and that the function's result only depends on its
    parameters. Note that the ordering of parameters is important. e.g.
    my_function(x = 1, y = 2), my_function(y = 2, x = 1), and my_function(1,2)
    will each be cached separately.

    Usage:

    @cache_results(600)
    def my_expensive_function(param1, param2, paarm3):
        ....
        return expensive_result

    """
    def do_cache(f):
        def x(*args, **kwargs):
            key = sha1(str(f.__module__) + str(f.__name__) + str(args) + str(kwargs)).hexdigest()
            result = _djcache.get(key)
            if result is None:
                result = f(*args, **kwargs)
                _djcache.set(key, result, seconds)
            return result
        return x
    return do_cache

The monkey patch:

djmoney_rates.utils.get_rate = cache_results(100)(djmoney_rates.utils.get_rate)
djmoney_rates.utils.get_rate_source = cache_results(100)(djmoney_rates.utils.get_rate_source)

It's difficult to know exactly how this functionality should be integrated into django-money-rates.