dot Redis 8 来了——而且它是开源的

了解更多

隆重推出适用于 Python 的 Redis OM

适用于 Redis 和 Python 的直观对象映射和流畅查询

我很高兴向大家介绍适用于 Python 的 Redis OM,这是一个功能强大的、面向开发者的新 Redis 库,它提供了对象映射、数据验证等功能。

Redis OM for Python 的这个预览版允许您使用声明式模型来建模数据,这对于 SQLAlchemy、Peewee 和 Django ORM 等对象关系映射器 (ORM) 的用户来说会感到非常熟悉。

但还有更多!每个 Redis OM 模型也是一个 Pydantic 模型,因此您可以使用 Pydantic 强大且可扩展的数据验证功能。此外,Redis OM 模型适用于任何 Python 库期望 Pydantic 模型的地方。因此,您可以将 Redis OM 模型与 FastAPI 一起使用,以自动验证 API 端点并为其生成 API 文档。

另一个我很喜欢的功能是 Redis OM 支持流畅的查询表达式和二级索引。Redis OM for Python 还在同一个库中同时支持异步 (asyncio) 和同步编程。而且令人惊叹的功能列表还在不断增加!

继续阅读,了解我是如何构建这个库的,以及一些关于关键功能的幕后细节。或者,如果您准备好尝试代码,请查看快速入门教程

适用于 Redis 的声明式模型

开发者通常通过客户端库访问 Redis,创建 Redis 数据结构(例如,哈希),然后对其执行命令

许多人喜欢这种基于命令的接口,因为它比使用关系型数据库编写 SQL 查询更简单。但是您上次写 SQL 是什么时候?使用现代 Web 框架的开发者倾向于使用 ORM,特别是使用声明式模型。

我喜欢 ORM 的地方在于,它们消除了许多与您正在尝试解决的问题无关的复杂性。我们创建 Redis OM,这样您最终可以在 Redis 中获得同样的体验。

哈希还是 JSON,任君选择

适用于 Python 的 Redis OM 包含两个可用于构建模型的基础模型类:HashModelJsonModel。

开源 Redis 用户可以使用 HashModel 将数据存储为 Redis 哈希,而安装了 RedisJSON Redis 模块或使用Redis Enterprise Cloud 或 Software 的用户可以使用 JsonModel 将数据原生存储为 JSON 对象。稍后我将详细讨论这些类的区别,但现在,我们将使用 HashModel。

简洁的模型定义

请看这段 Redis OM 示例代码,它定义了一个 Customer 模型并使用它将数据保存到 Redis

import datetime
from typing import Optional

from pydantic import EmailStr

from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: EmailStr
    join_date: datetime.date
    age: int
    bio: Optional[str]


# First, we create a new `Customer` object:
andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="[email protected]",
    join_date=datetime.date.today(),
    age=38,
    bio="Python developer, works at Redis, Inc."
)

# The model generates a globally unique primary key automatically
# without needing to talk to Redis.
print(andrew.pk)
# > '01FJM6PH661HCNNRC884H6K30C'

# We can save the model to Redis by calling `save()`:
andrew.save()

# To retrieve this customer with its primary key, we use `Customer.get()`:
assert Customer.get(andrew.pk) == andrew

这个简洁的模型定义立即为您提供了诸如 `get()` 和 `save()` 等方法。在幕后,这些方法管理 Redis 哈希中的数据。

然而,Redis OM 的功能远不止于此。它还会生成全局唯一且可排序的主键。这部分功能非常有用,所以我来解释一下它是如何工作的。

全局唯一主键

Redis OM 会自动为每个模型实例生成一个全局唯一的主键。您可以使用此主键在 Redis 中保存和检索模型数据。

这些主键保证是全局唯一的,但它们也是完全在客户端生成的,无需向 Redis 发出任何请求。它们还具有可排序和紧凑的特性。这一切都归功于通用唯一字典序可排序标识符 (ULID) 规范。

Redis OM 主键是 ULID,这得益于python-ulid。您可以在此处阅读有关 ULID 规范的更多信息。这非常酷!

然而,除了这些持久化功能之外,您还可以通过Pydantic 获得数据验证。让我们深入了解验证的工作原理。

使用 Pydantic 进行数据验证

Redis 与关系型数据库的一个区别在于 Redis 不强制执行模式,因此您可以在 Redis 中写入一个字符串,稍后用一个数字覆盖它。这比关系型数据库更灵活,但也意味着应用程序负责数据验证。

我们认为您不应该费力去寻找在应用程序中处理验证的最佳方法,因此每个 Redis OM 模型也都是一个 Pydantic 模型。这意味着您可以根据模型中的类型提示获得 Pydantic 验证,并且可以通过标准的 Pydantic 钩子(包括自定义验证器)来控制验证。

这是一些演示验证如何工作的示例代码

import datetime
from typing import Optional

from pydantic import EmailStr, ValidationError

from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: EmailStr
    join_date: datetime.date
    age: int
    bio: Optional[str]


try:
    Customer(
        first_name="Andrew",
        last_name="Brookins",
        email="Not an email address!",
        join_date=datetime.date.today(),
        age=38,
        bio="Python developer, works at Redis, Inc."
    )
except ValidationError as e:
    print(e)
    """
    pydantic.error_wrappers.ValidationError: 1 validation error for Customer
     email
       value is not a valid email address (type=value_error.email)
    """

如果适用于 Python 的 Redis OM 只为您提供了持久化方法和数据验证,我会觉得它已经相当不错了。但我们想为您处理更多复杂性,为此我们需要帮助您编写富有表达力的查询,就像您使用 ORM 一样。接下来,我将谈谈这些查询的工作原理。

流畅的查询表达式

ORM 不仅为您提供声明式模型。它们还提供了一个 API,让您可以根据主键以外的属性来查询数据。想象一下,查找某个年龄以上的所有客户、某个日期之前注册的客户等等。

开箱即用,Redis 非常擅长使用主键查找数据。毕竟,它是一个键值存储,其值是数据结构。但 Redis 不包含查询和二级索引系统,因此如果您想对数据进行索引和查询,则必须以复杂的方式自行管理索引。

同样,我们想为您处理这种复杂性,因此我们在一个重要的 Redis 模块之上构建了流畅的查询表达式:RediSearch。RediSearch 是一个源代码可用的模块,它提供了 Redis 所缺少的查询和索引功能。

让我们看看如果在我们的 Customer 模型上将一些字段标记为 `index=True` 会发生什么。现在,我们可以使用该模型进行查询

import datetime
from typing import Optional

from pydantic import EmailStr

from redis_om import (
    Field,
    HashModel,
    Migrator
)
from redis_om import get_redis_connection

                 
class Customer(HashModel):
    first_name: str
    last_name: str = Field(index=True)
    email: EmailStr
    join_date: datetime.date
    age: int = Field(index=True)
    bio: Optional[str]


# Now, if we use this model with a Redis deployment that has the
# RediSearch module installed, we can run queries like the following.

# Before running queries, we need to run migrations to set up the
# indexes that Redis OM will use. You can also use the `migrate`
# CLI tool for this!
redis = get_redis_connection()
Migrator(redis).run()

# Find all customers with the last name "Brookins"
Customer.find(Customer.last_name == "Brookins").all()

# Find all customers that do NOT have the last name "Brookins"
Customer.find(Customer.last_name != "Brookins").all()

# Find all customers whose last name is "Brookins" OR whose age is 
# 100 AND whose last name is "Smith"
Customer.find((Customer.last_name == "Brookins") | (
        Customer.age == 100
) & (Customer.last_name == "Smith")).all()

这种表达式语法可能看起来很熟悉——它是PeeweeSQLAlchemyDjango ORM 中我喜欢的所有内容的混合体。

嵌入式模型

当您使用 Redis 对复杂数据进行建模时,您不可避免地会想要存储嵌入式数据。如果您使用 Redis 哈希来建模客户数据,您可能想将客户的送货地址等数据存储在单个客户的哈希中。不幸的是,Redis 哈希无法存储嵌套容器,例如列表、集合或其他哈希,因此这不起作用。

在这里,将数据存储为原生 JSON 对象非常有意义。如果您将客户数据建模为 JSON 文档,则可以将所需的任何内容嵌入到单个客户的记录中。

然而,Redis 原生不支持 JSON。这正是我们创建源代码可用的RedisJSON 模块的原因。RedisJSON 使得将 Redis 用作文档数据库,轻松存储和查询复杂的 JSON 对象成为可能。

使用适用于 Python 的 Redis OM,如果您的 Redis 实例安装了 RedisJSON,您可以使用 JsonModel 类。这个模型类允许您将 JsonModels 嵌入到其他 JsonModels 中。想象一下,客户有一个订单数组,每个订单又有一个项目数组,依此类推。

以下是适用于 Python 的 Redis OM 中嵌入式 JSON 模型的样子

import datetime
from typing import Optional

from redis_om import (
    EmbeddedJsonModel,
    JsonModel,
    Field,
    Migrator,
)
from redis_om import get_redis_connection


class Address(EmbeddedJsonModel):
    address_line_1: str
    address_line_2: Optional[str]
    city: str = Field(index=True)
    state: str = Field(index=True)
    country: str
    postal_code: str = Field(index=True)


class Customer(JsonModel):
    first_name: str = Field(index=True)
    last_name: str = Field(index=True)
    email: str = Field(index=True)
    join_date: datetime.date
    age: int = Field(index=True)
    bio: Optional[str] = Field(index=True, full_text_search=True,
                               default="")

    # Creates an embedded model.
    address: Address


# With these two models and a Redis deployment with the RedisJSON 
# and RediSearch modules installed, we can run queries like the
# following.

# Before running queries, we need to run migrations to set up the
# indexes that Redis OM will use. You can also use the `migrate`
# CLI tool for this!
redis = get_redis_connection()
Migrator(redis).run()

# Find all customers who live in San Antonio, TX
Customer.find(Customer.address.city == "San Antonio",
              Customer.address.state == "TX")

您不仅可以灵活存储复杂的 JSON 对象,而且适用于 Python 的 Redis OM 还能识别这些嵌套结构,并允许您对其编写查询表达式。太棒了!

试用适用于 Python 的 Redis OM

我希望您能感受到我对适用于 Python 的 Redis OM 有多兴奋。我致力于将当前 Python 生态系统中一些最好的东西整合起来,为 Redis 开发者处理那些我认为没有人应该处理的复杂性。

如果我激起了您的兴趣,请查看快速入门教程。适用于 Python 的 Redis OM 处于一个非常早期的阶段,我们称之为“预览版”。因此,存在一些粗糙之处,您可能会遇到错误,并且我们仍在努力提供完整的文档。但愿景已经明确,我鼓励您去了解一下。

最后,我想说的是,我们从声明式数据模型开始,但我们还有很多想要构建的功能——无论是数据建模还是其他方面。敬请期待更多令人兴奋的 Redis OM 功能!