跳转到主要内容

Django工具,用于使用Django缓存框架的memoization装饰器。

项目描述

  • 许可证: MPL 2.0

Build Status Documentation Status https://img.shields.io/badge/code%20style-black-000000.svg

Django工具,用于使用Django缓存框架的memoization装饰器。

有关Python和Django的版本,请查看tox.ini文件

主要功能

  • memoized函数调用可以被无效化。

  • 与非平凡参数和关键字参数一起工作

  • 通过回调了解缓存命中和缓存未命中

  • 当存储函数结果不重要或不需要时,可作为“保护”重复执行。

安装

pip install django-cache-memoize

用法

# Import the decorator
from cache_memoize import cache_memoize

# Attach decorator to cacheable function with a timeout of 100 seconds.
@cache_memoize(100)
def expensive_function(start, end):
    return random.randint(start, end)

# Just a regular Django view
def myview(request):
    # If you run this view repeatedly you'll get the same
    # output every time for 100 seconds.
    return http.HttpResponse(str(expensive_function(0, 100)))

缓存使用Django的默认缓存框架。最终,它调用django.core.cache.cache.set(cache_key, function_out, expiration)。所以如果您有一个返回无法pickle和缓存的函数,它将不起作用。

对于这类情况,Django公开了一个简单、底层的缓存API。您可以使用此API以您喜欢的任何粒度级别在缓存中存储对象。您可以缓存任何可以安全pickle的Python对象:字符串、字典、模型对象列表等。(大多数常见的Python对象都可以pickle;有关pickle的更多信息,请参阅Python文档。)

请参阅文档

示例用法

这篇博客文章:如何使用django-cache-memoize

它展示了类似于上面的用法示例,但细节更多。特别是,它展示了在不使用django-cache-memoize的情况下与添加到代码后的区别。

高级用法

args_rewrite

在内部,装饰器将包装函数的每个参数和关键字参数重写为一个连接字符串。您可能想要做的第一件事是帮助装饰器将参数重写为更适合作为缓存键字符串的内容。例如,假设您有一个类的实例,其__str__方法不返回唯一值。例如

class Record(models.Model):
    name = models.CharField(max_length=100)
    lastname = models.CharField(max_length=100)
    friends = models.ManyToManyField(SomeOtherModel)

    def __str__(self):
        return self.name

# Example use:
>>> record = Record.objects.create(name='Peter', lastname='Bengtsson')
>>> print(record)
Peter
>>> record2 = Record.objects.create(name='Peter', lastname='Different')
>>> print(record2)
Peter

这是一个人为的例子,但基本上您知道某些参数的str()转换是不安全的。然后您可以传递一个名为args_rewrite的可调用对象。它接收与您正在装饰的函数相同的定位参数和关键字参数。以下是一个示例实现

from cache_memoize import cache_memoize

def count_friends_args_rewrite(record):
    # The 'id' is always unique. Use that instead of the default __str__
    return record.id

@cache_memoize(100, args_rewrite=count_friends_args_rewrite)
def count_friends(record):
    # Assume this is an expensive function that can be memoize cached.
    return record.friends.all().count()

prefix

默认情况下,前缀是函数的名称。考虑

from cache_memoize import cache_memoize

@cache_memoize(10, prefix='randomness')
def function1():
    return random.random()

@cache_memoize(10, prefix='randomness')
def function2():  # different name, same arguments, same functionality
    return random.random()

# Example use
>>> function1()
0.39403406043780986
>>> function1()
0.39403406043780986
>>> # ^ repeated of course
>>> function2()
0.39403406043780986
>>> # ^ because the prefix was forcibly the same, the cache key is the same

hit_callable

如果设置,当缓存能够找到并返回缓存命中时,将调用一个带有原始参数和关键字参数的函数。例如,假设您想告诉您的statsd服务器每次缓存命中时。

from cache_memoize import cache_memoize

def _cache_hit(user, **kwargs):
    statsdthing.incr(f'cachehit:{user.id}', 1)

@cache_memoize(10, hit_callable=_cache_hit)
def calculate_tax(user, tax=0.1):
    return ...

miss_callable

hit_callable完全相同的功能,除了它在不是缓存命中时调用。

store_result

如果有一个函数您想确保只在超时到期时调用一次,但实际上并不太关心函数的返回值,这可能是因为您知道该函数返回的内容会很快填满您的memcached,或者您知道它返回的内容不能pickle。那么您可以设置store_resultFalse。这相当于您的函数返回True

from cache_memoize import cache_memoize

@cache_memoize(1000, store_result=False)
def send_tax_returns(user):
    # something something time consuming
    ...
    return some_none_pickleable_thing

def myview(request):
    # View this view as much as you like the 'send_tax_returns' function
    # won't be called more than once every 1000 seconds.
    send_tax_returns(request.user)

cache_exceptions

如果您的函数可以抛出异常作为有效结果,这很有用。如果缓存的函数抛出指定的任何异常,则异常被缓存并正常抛出。后续的缓存调用将立即重新抛出异常,函数将不会执行。cache_exceptions接受一个异常或异常元组。

此选项允许您像缓存其他结果一样缓存这些异常。只有从提供的缓存异常类列表中抛出的异常被缓存,其他异常立即传播。

>>> from cache_memoize import cache_memoize

>>> class InvalidParameter(Exception):
...     pass

>>> @cache_memoize(1000, cache_exceptions=(InvalidParameter, ))
... def run_calculations(parameter):
...     # something something time consuming
...     raise InvalidParameter

>>> run_calculations(1)
Traceback (most recent call last):
...
InvalidParameter

# run_calculations will now raise InvalidParameter immediately
# without running the expensive calculation
>>> run_calculations(1)
Traceback (most recent call last):
...
InvalidParameter

cache_alias

cache_alias参数允许您使用除默认缓存之外的缓存。

# Given settings like:
# CACHES = {
#     'default': {...},
#     'other': {...},
# }

@cache_memoize(1000, cache_alias='other')
def myfunc(start, end):
    return random.random()

缓存失效

当您想要“撤销”一些缓存操作时,只需再次以相同的参数调用该函数即可,只是您需要在函数后面添加.invalidate

from cache_memoize import cache_memoize

@cache_memoize(10)
def expensive_function(start, end):
    return random.randint(start, end)

>>> expensive_function(1, 100)
65
>>> expensive_function(1, 100)
65
>>> expensive_function(100, 200)
121
>>> exensive_function.invalidate(1, 100)
>>> expensive_function(1, 100)
89
>>> expensive_function(100, 200)
121

另一种做同样事情的方法是传递一个关键字参数_refresh=True。如下所示

# Continuing from the code block above
>>> expensive_function(100, 200)
121
>>> expensive_function(100, 200, _refresh=True)
177
>>> expensive_function(100, 200)
177

无法清除多个缓存键。在上面的示例中,当您想使缓存失效时,必须知道“原始参数”。没有“搜索”所有匹配特定模式缓存键的方法。

兼容性

  • Python 3.8, 3.9, 3.10 & 3.11

  • Django 3.2, 4.1 & 4.2

请查看tox.ini文件以获取更详细的兼容性测试覆盖情况。

已有技术

历史

Mozilla 符号服务器是用 Django 编写的。它是一个介于 C++ 调试器和 AWS S3 之间的网络服务。它在 AWS S3 中移动符号文件。符号文件对于 C++(和其他编译语言)来说,就像源映射对于 JavaScript 一样。

该服务流量很大。下载流量(代理 S3 中的符号请求)大约每秒有 ~40 个请求。由于应用程序的性质,大多数这些 GET 请求都会返回 404 Not Found,但不是每个文件都从 AWS S3 请求,这些查找被缓存在高度配置的 Redis 配置中。这个 Redis 缓存还连接到上传新文件的代码部分。

新上传的文件是以文件包的形式出现的 zip 文件,来自 Mozilla 的构建系统,每分钟大约有 600MB,每个包含平均约 100 个文件。当收到新的上传时,我们需要快速找到它在 S3 中是否存在,并且由于经常有相同的文件在不同上传中重复,所以这些查找被缓存。但是,当文件被上传到 S3 时,我们需要快速且自信地使任何本地缓存失效。这样就可以保持一个非常积极的缓存,而没有过期的时期。

这就是 django-cache-memoize 构建和测试用例。它最初是为 Python 3.6 和 Django 1.11 编写的,但在提取后,与 Python 2.7 和 Django 1.8 兼容。

django-cache-memoize 还用于 SongSear.ch,以缓存自动完成搜索输入中的简短查询。所有自动完成都是由 Elasticsearch 完成的,它非常快,但不如 memcached 快。

“竞争”

已经有 django-memoize,由 Thomas Vavrys 提供。它也是一个用于 Django 的缓存装饰器。它使用默认的缓存框架作为存储。它使用 inspect 对装饰的函数进行操作以构建缓存键。

在运行 django-memoizedjango-cache-memoize 的基准测试中,我发现 django-cache-memoize 平均快 ~4 倍

另一个关键的区别是,django-cache-memoize 使用 str(),而 django-memoize 使用 repr(),在可变对象(例如类实例)作为参数的某些情况下,缓存将不起作用。例如,这 django-memoize 中工作。

from memoize import memoize

@memoize(60)
def count_user_groups(user):
    return user.groups.all().count()

def myview(request):
    # this will never be memoized
    print(count_user_groups(request.user))

但是,这会工作…

from cache_memoize import cache_memoize

@cache_memoize(60)
def count_user_groups(user):
    return user.groups.all().count()

def myview(request):
    # this *will* work as expected
    print(count_user_groups(request.user))

开发

最基本的事情是克隆存储库并运行

pip install -e ".[dev]"
tox

代码风格是 all black

所有代码都必须使用 Black 格式化,检查此问题的最佳工具是 therapist,因为它可以帮助您运行所有操作,帮助您解决问题,并确保在您 git 提交之前通过 linting。此项目还使用 flake8 检查 Black 无法检查的其他事项。

要使用 tox 检查 linting,请使用

tox -e lint-py36

要安装 therapist pre-commit 钩子,只需运行

therapist install

当您运行 therapist run 时,它只会检查您修改过的文件。要检查所有文件,请使用

therapist run --use-tracked-files

要修复所有/任何问题,请运行

therapist run --use-tracked-files --fix

项目详情


下载文件

下载适用于您平台文件。如果您不确定该选择哪个,请了解更多关于 安装包 的信息。

源分布

django-cache-memoize-0.2.0.tar.gz (14.6 kB 查看哈希值)

上传

构建分布

django_cache_memoize-0.2.0-py3-none-any.whl (14.4 kB 查看哈希值)

上传 Python 3

由以下支持