跳转到主要内容

Django的全功能redis缓存后端。

项目描述

Jazzband GitHub Actions Coverage https://img.shields.io/pypi/v/django-redis.svg?style=flat

这是一个Jazzband项目。通过贡献,您同意遵守贡献者行为准则并遵循指南

简介

django-redis 是一个BSD许可的开源、功能齐全的Redis缓存和会话后端,适用于Django。

为什么使用django-redis?

  • 使用原生redis-py URL符号连接字符串

  • 可插拔客户端

  • 可插拔解析器

  • 可插拔序列化器

  • 默认客户端支持主/副节点

  • 全面的测试套件

  • 已在多个项目中作为缓存和会话存储在生产环境中使用

  • 支持无限超时

  • 提供对Redis客户端/连接池的原始访问功能

  • 高度可配置(例如,可以模拟memcached异常行为)

  • 默认支持Unix套接字

需求

用户指南

安装

使用pip安装

$ python -m pip install django-redis

配置为缓存后端

要开始使用django-redis,您应将Django缓存设置更改为以下类似的内容

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

django-redis使用redis-py原生URL符号进行连接字符串,这允许更好的互操作性,并以更“标准”的方式使用连接字符串。以下是一些示例

  • redis://[[用户名]:[密码]]@localhost:6379/0

  • rediss://[[用户名]:[密码]]@localhost:6379/0

  • unix://[[用户名]:[密码]]@/path/to/socket.sock?db=0

支持三种URL方案

  • redis://:创建一个正常的TCP套接字连接

  • rediss://:创建一个SSL包装的TCP套接字连接

  • unix://:创建一个Unix域套接字连接

指定数据库编号的几种方法

  • A db 查询字符串选项,例如 redis://localhost?db=0

  • 如果使用 redis:// 方案,则URL的路径参数,例如 redis://localhost/0

当使用Redis的ACL时,您需要将用户名添加到URL中(并且通过Cache OPTIONS提供密码)。用户 django 的登录方式如下

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://django@localhost:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "mysecret"
        }
    }
}

另一种方法是同时将用户名和密码写入URL

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://django:mysecret@localhost:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

在某些情况下,您用于连接Redis的密码可能不是URL安全的,在这种情况下,您可以对其进行转义或只需在 OPTIONS 字典中使用便利选项即可

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "mysecret"
        }
    }
}

请注意,此选项不会覆盖uri中的密码,因此如果您已在uri中设置了密码,则此设置将被忽略。

配置为会话后端

Django默认可以使用任何缓存后端作为会话后端,您可以通过使用django-redis作为会话存储的后端来从中受益,而无需安装任何额外的后端

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

使用django-redis进行测试

django-redis支持自定义底层Redis客户端(请参阅“可插拔客户端”)。这可以用于测试目的。

如果您想在测试后从缓存中清除所有数据,请将以下行添加到您的测试类中

from django_redis import get_redis_connection

def tearDown(self):
    get_redis_connection("default").flushall()

高级用法

Pickle版本

对于几乎所有值,django-redis都使用pickle来序列化对象。

默认使用pickle的pickle.DEFAULT_PROTOCOL版本,以确保安全升级和跨Python版本的兼容性。如果您想设置一个具体的版本,可以使用PICKLE_VERSION选项

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "PICKLE_VERSION": -1  # Will use highest protocol version available
        }
    }
}

套接字超时

可以使用SOCKET_TIMEOUTSOCKET_CONNECT_TIMEOUT选项来设置套接字超时

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "SOCKET_CONNECT_TIMEOUT": 5,  # seconds
            "SOCKET_TIMEOUT": 5,  # seconds
        }
    }
}

SOCKET_CONNECT_TIMEOUT是建立连接的超时时间,SOCKET_TIMEOUT是连接建立后读取和写入操作的超时时间。

压缩支持

django-redis默认带有压缩支持,但默认情况下是禁用的。您可以通过设置具体的后端来激活它

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
        }
    }
}

让我们看看如何使用lzma压缩格式使其工作

import lzma

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "COMPRESSOR": "django_redis.compressors.lzma.LzmaCompressor",
        }
    }
}

Lz4压缩支持(需要lz4库)

import lz4

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "COMPRESSOR": "django_redis.compressors.lz4.Lz4Compressor",
        }
    }
}

Zstandard (zstd)压缩支持(需要pyzstd库)

import pyzstd

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "COMPRESSOR": "django_redis.compressors.zstd.ZStdCompressor",
        }
    }
}

Memcached 异常行为

在某些情况下,当 Redis 仅用作缓存时,您可能不希望在 Redis 服务器宕机时抛出异常。这是 memcached 后端默认行为,也可以在 django-redis 中模拟。

要设置类似于 memcached 的行为(忽略连接异常),您应该在缓存配置中设置 IGNORE_EXCEPTIONS 设置。

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "IGNORE_EXCEPTIONS": True,
        }
    }
}

此外,您可以将相同的设置应用于所有配置的缓存,您可以在设置中设置全局标志。

DJANGO_REDIS_IGNORE_EXCEPTIONS = True

记录被忽略的异常

当使用 IGNORE_EXCEPTIONSDJANGO_REDIS_IGNORE_EXCEPTIONS 忽略异常时,您可以选择在设置文件中使用全局变量 DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS 记录异常。

DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True

如果您想指定输出异常的记录器,只需将全局变量 DJANGO_REDIS_LOGGER 设置为所需的记录器字符串名称和/或路径。如果没有指定记录器且 DJANGO_REDIS_LOG_IGNORED_EXCEPTIONSTrue,则默认为 __name__

DJANGO_REDIS_LOGGER = 'some.specified.logger'

无限超时

django-redis 默认支持无限超时。它以与 django 后端合同规定相同的方式运行。

  • timeout=0 立即使值过期。

  • timeout=None 无限超时

cache.set("key", "value", timeout=None)

从键获取 TTL(生存时间)

使用 Redis,您可以访问任何存储键的 TTL,为此,django-redis 暴露了 ttl 函数。

它返回

  • 0 如果键不存在(或已过期)。

  • None 对于存在但没有任何过期的键。

  • 任何可挥发的键的 TTL 值(任何具有过期的键)。

>>> from django.core.cache import cache
>>> cache.set("foo", "value", timeout=25)
>>> cache.ttl("foo")
25
>>> cache.ttl("not-existent")
0

使用 Redis,您可以以毫秒为单位访问任何存储键的 TTL,为此,django-redis 暴露了 pttl 函数。

>>> from django.core.cache import cache
>>> cache.set("foo", "value", timeout=25)
>>> cache.pttl("foo")
25000
>>> cache.pttl("not-existent")
0

过期 & 持久化

除了简单的 ttl 查询外,您还可以使用 persistexpire 方法持久化具体的键或指定新的过期超时。

>>> cache.set("foo", "bar", timeout=22)
>>> cache.ttl("foo")
22
>>> cache.persist("foo")
True
>>> cache.ttl("foo")
None
>>> cache.set("foo", "bar", timeout=22)
>>> cache.expire("foo", timeout=5)
True
>>> cache.ttl("foo")
5

使用 expire_at 方法可以使键在特定的时间点过期。

>>> cache.set("foo", "bar", timeout=22)
>>> cache.expire_at("foo", datetime.now() + timedelta(hours=1))
True
>>> cache.ttl("foo")
3600

使用 pexpire_at 方法可以使键在特定的时间点过期,具有毫秒级精度。

>>> cache.set("foo", "bar", timeout=22)
>>> cache.pexpire_at("foo", datetime.now() + timedelta(milliseconds=900, hours=1))
True
>>> cache.ttl("foo")
3601
>>> cache.pttl("foo")
3600900

使用 pexpire 方法可以提供毫秒级精度。

>>> cache.set("foo", "bar", timeout=22)
>>> cache.pexpire("foo", timeout=5500)
True
>>> cache.pttl("foo")
5500

它还支持 Redis 创建 Redis 分布式命名锁的能力。锁接口与 threading.Lock 相同,因此您可以将其用作替代品。

with cache.lock("somekey"):
    do_some_thing()

批量扫描 & 删除键

django-redis 附带一些额外的辅助方法,可以帮助使用 glob 模式搜索或删除键。

>>> from django.core.cache import cache
>>> cache.keys("foo_*")
["foo_1", "foo_2"]

这种简单的搜索将返回所有匹配的值。在具有大量键的数据库中,这不是一个合适的方法。相反,您可以使用 iter_keys 函数,它类似于 keys 函数,但使用 Redis 服务器端游标。调用 iter_keys 将返回一个生成器,然后您可以高效地遍历它。

>>> from django.core.cache import cache
>>> cache.iter_keys("foo_*")
<generator object algo at 0x7ffa9c2713a8>
>>> next(cache.iter_keys("foo_*"))
"foo_1"

对于删除键,您应该使用 delete_pattern,它具有与 keys 函数相同的 glob 模式语法,并返回已删除键的数量。

>>> from django.core.cache import cache
>>> cache.delete_pattern("foo_*")

为了在删除大量键时实现最佳性能,您应该在 Django 设置中将 DJANGO_REDIS_SCAN_ITERSIZE 设置为一个相对较高的数字(例如,100_000),默认情况下在 Django 设置中或在 delete_pattern 中直接传递。

>>> from django.core.cache import cache
>>> cache.delete_pattern("foo_*", itersize=100_000)

Redis 本地命令

django-redis 对一些 Redis 原子操作有限支持,例如 SETNXINCR 命令。

您可以通过后端的 set() 方法使用 SETNX 命令,并带有 nx 参数。

>>> from django.core.cache import cache
>>> cache.set("key", "value1", nx=True)
True
>>> cache.set("key", "value2", nx=True)
False
>>> cache.get("key")
"value1"

此外,当键包含的值适合使用时,incrdecr 方法会使用 Redis 原子操作。

原始客户端访问

在某些情况下,您的应用程序需要访问原始 Redis 客户端来使用 Django 缓存界面未公开的一些高级功能。为了避免为创建原始连接存储另一个设置,django-redis 提供了函数,您可以重复使用缓存连接字符串来获取原始客户端:get_redis_connection(alias)

>>> from django_redis import get_redis_connection
>>> con = get_redis_connection("default")
>>> con
<redis.client.Redis object at 0x2dc4510>

警告:并非所有可插拔客户端都支持此功能。

连接池

在幕后,django-redis 使用底层的 redis-py 连接池实现,并公开了一种简单的方式来配置它。或者,您可以直接自定义后端的数据连接/连接池创建。

redis-py 的默认行为是不关闭连接,在可能的情况下回收它们。

配置默认连接池

默认连接池很简单。例如,您可以通过在 CACHES 设置中设置 CONNECTION_POOL_KWARGS 来自定义池中连接的最大数量。

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # ...
        "OPTIONS": {
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
        }
    }
}

您可以使用以下代码片段验证池已打开多少连接

from django_redis import get_redis_connection

r = get_redis_connection("default")  # Use the name you have defined for Redis in settings.CACHES
connection_pool = r.connection_pool
print("Created connections so far: %d" % connection_pool._created_connections)

由于默认连接池将其连接不使用的所有关键字参数传递给其连接,您还可以通过将这些选项添加到 CONNECTION_POOL_KWARGS 来自定义池创建的连接。

CACHES = {
    "default": {
        # ...
        "OPTIONS": {
            "CONNECTION_POOL_KWARGS": {"max_connections": 100, "retry_on_timeout": True}
        }
    }
}

使用自己的连接池子类

有时您想使用自己的连接池子类。使用 django-redis 的后端选项中的 CONNECTION_POOL_CLASS 参数可以实现这一点。

from redis.connection import ConnectionPool

class MyOwnPool(ConnectionPool):
    # Just doing nothing, only for example purpose
    pass
# Omitting all backend declaration boilerplate code.

"OPTIONS": {
    "CONNECTION_POOL_CLASS": "myproj.mypool.MyOwnPool",
}

自定义连接工厂

如果您对上述任何一种方法都不满意,您可以在 django-redis 连接工厂过程中进行干预,并对其进行自定义或完全重写。

默认情况下,django-redis 通过在全局 Django 设置 DJANGO_REDIS_CONNECTION_FACTORY 中指定的 django_redis.pool.ConnectionFactory 类来创建连接。

class ConnectionFactory(object):
    def get_connection_pool(self, params: dict):
        # Given connection parameters in the `params` argument, return new
        # connection pool. It should be overwritten if you want do
        # something before/after creating the connection pool, or return
        # your own connection pool.
        pass

    def get_connection(self, params: dict):
        # Given connection parameters in the `params` argument, return a
        # new connection. It should be overwritten if you want to do
        # something before/after creating a new connection. The default
        # implementation uses `get_connection_pool` to obtain a pool and
        # create a new connection in the newly obtained pool.
        pass

    def get_or_create_connection_pool(self, params: dict):
        # This is a high layer on top of `get_connection_pool` for
        # implementing a cache of created connection pools. It should be
        # overwritten if you want change the default behavior.
        pass

    def make_connection_params(self, url: str) -> dict:
        # The responsibility of this method is to convert basic connection
        # parameters and other settings to fully connection pool ready
        # connection parameters.
        pass

    def connect(self, url: str):
        # This is really a public API and entry point for this factory
        # class. This encapsulates the main logic of creating the
        # previously mentioned `params` using `make_connection_params` and
        # creating a new connection using the `get_connection` method.
        pass

使用哨兵连接工厂

为了便于使用 Redis 哨兵,django-redis 内置了哨兵连接工厂,该工厂创建哨兵连接池。要启用此功能,您应该添加以下内容

# Enable the alternate connection factory.
DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory'

# These sentinels are shared between all the examples, and are passed
# directly to redis Sentinel. These can also be defined inline.
SENTINELS = [
    ('sentinel-1', 26379),
    ('sentinel-2', 26379),
    ('sentinel-3', 26379),
]

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # The hostname in LOCATION is the primary (service / master) name
        "LOCATION": "redis://service_name/db",
        "OPTIONS": {
            # While the default client will work, this will check you
            # have configured things correctly, and also create a
            # primary and replica pool for the service specified by
            # LOCATION rather than requiring two URLs.
            "CLIENT_CLASS": "django_redis.client.SentinelClient",

            # Sentinels which are passed directly to redis Sentinel.
            "SENTINELS": SENTINELS,

            # kwargs for redis Sentinel (optional).
            "SENTINEL_KWARGS": {},

            # You can still override the connection pool (optional).
            "CONNECTION_POOL_CLASS": "redis.sentinel.SentinelConnectionPool",
        },
    },

    # A minimal example using the SentinelClient.
    "minimal": {
        "BACKEND": "django_redis.cache.RedisCache",

        # The SentinelClient will use this location for both the primaries
        # and replicas.
        "LOCATION": "redis://minimal_service_name/db",

        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.SentinelClient",
            "SENTINELS": SENTINELS,
        },
    },

    # A minimal example using the DefaultClient.
    "other": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": [
            # The DefaultClient is [primary, replicas...], but with the
            # SentinelConnectionPool it only requires one "is_master=0".
            "redis://other_service_name/db?is_master=1",
            "redis://other_service_name/db?is_master=0",
        ],
        "OPTIONS": {"SENTINELS": SENTINELS},
    },

    # A minimal example only using only replicas in read only mode (and
    # the DefaultClient).
    "readonly": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://readonly_service_name/db?is_master=0",
        "OPTIONS": {"SENTINELS": SENTINELS},
    },
}

也可以将一些缓存设置为哨兵,而将另一些设置为不是。

SENTINELS = [
    ('sentinel-1', 26379),
    ('sentinel-2', 26379),
    ('sentinel-3', 26379),
]
CACHES = {
    "sentinel": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://service_name/db",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.SentinelClient",
            "SENTINELS": SENTINELS,
            "CONNECTION_POOL_CLASS": "redis.sentinel.SentinelConnectionPool",
            "CONNECTION_FACTORY": "django_redis.pool.SentinelConnectionFactory",
        },
    },
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        },
    },
}

可插拔解析器

redis-py(django-redis 使用的 Python Redis 客户端)附带了一个纯 Python Redis 解析器,对于大多数常见任务来说效果很好,但如果您想获得一些性能提升,可以使用 hiredis。

hiredis 是用 C 编写的 Redis 客户端,它有一个自己的解析器,可以与 django-redis 一起使用。

"OPTIONS": {
    "PARSER_CLASS": "redis.connection.HiredisParser",
}

注意:如果您使用的是 redis-py 的第 5 版,由于该包内部类别的重命名,请在 PARSER_CLASS 中使用 "redis.connection._HiredisParser"

可插拔客户端

django-redis 被设计得非常灵活和可配置。为此,它公开了可插拔的后端,这使得扩展默认行为变得容易,并且它自带了一些。

默认客户端

关于默认客户端的解释几乎都在这里,只有一个例外:默认客户端自带复制支持。

要连接到 Redis 复制设置,您应该将 LOCATION 改为如下所示

"LOCATION": [
    "redis://127.0.0.1:6379/1",
    "redis://127.0.0.1:6378/1",
]

第一个连接字符串表示主服务器,其余的是从服务器。

警告:复制设置在生产环境中未经过充分的测试。

分片客户端

此可插拔客户端实现了客户端分片。它几乎继承了默认客户端的所有功能。要使用它,请将您的缓存设置更改为如下所示

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": [
            "redis://127.0.0.1:6379/1",
            "redis://127.0.0.1:6379/2",
        ],
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.ShardClient",
        }
    }
}

警告:分片客户端仍然是实验性的,因此在生产环境中使用时要小心。

群组客户端

此插件客户端有助于处理“雷鸣之群”问题。您可以在以下链接中了解更多信息:维基百科

与之前的插件客户端一样,它继承了默认客户端的所有功能,并增加了一些用于获取/设置键的额外方法。

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.HerdClient",
        }
    }
}

此客户端公开了额外的设置

  • CACHE_HERD_TIMEOUT:设置默认的群组超时时间。(默认值:60秒)

插件序列化器

插件客户端在将数据发送到服务器之前对其进行序列化。默认情况下,django-redis使用Python的pickle模块来序列化数据。这非常灵活,可以处理大量对象类型。

要使用JSON进行序列化,也可以使用JSONSerializer序列化器。

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "SERIALIZER": "django_redis.serializers.json.JSONSerializer",
        }
    }
}

还支持使用MsgPack进行序列化(需要msgpack库)

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "SERIALIZER": "django_redis.serializers.msgpack.MSGPackSerializer",
        }
    }
}

插件Redis客户端

django-redis默认使用Redis客户端redis.client.StrictClient。可以使用其他客户端。

您可以通过在CACHES设置中设置REDIS_CLIENT_CLASS来自定义使用的客户端。您还可以通过设置REDIS_CLIENT_KWARGS为此类提供参数。

CACHES = {
    "default": {
        "OPTIONS": {
            "REDIS_CLIENT_CLASS": "my.module.ClientClass",
            "REDIS_CLIENT_KWARGS": {"some_setting": True},
        }
    }
}

关闭连接

默认情况下,django-redis在close()方法中的行为是保持与Redis服务器的连接。

您可以通过在django设置中设置DJANGO_REDIS_CLOSE_CONNECTION = True来更改所有缓存的默认行为(全局)或在(缓存级别)通过为每个配置的缓存设置OPTIONS中的CLOSE_CONNECTION: True来设置。

将值设置为True将指示django-redis关闭所有连接(自v. 4.12.2以来),无论其当前使用情况如何。

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CLOSE_CONNECTION": True,
        }
    }
}

SSL/TLS和自签名证书

如果遇到提供使用自签名证书进行TLS连接的Redis服务器,可以使用以下方法禁用证书验证:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "rediss://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": None}
        }
    }
}

许可协议

Copyright (c) 2011-2015 Andrey Antukh <niwi@niwi.nz>
Copyright (c) 2011 Sean Bleier

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS`` AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

项目详情


发布历史 发布通知 | RSS订阅

下载文件

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

源分布

django-redis-5.4.0.tar.gz (52.6 kB 查看哈希值)

上传时间:

构建分布

django_redis-5.4.0-py3-none-any.whl (31.1 kB 查看哈希值)

上传时间: Python 3

由以下支持