学习

Redis OM for Python 入门

Andrew Brookins
作者
Andrew Brookins, 前 Redis 课程软件工程师

Redis OM for Python 使您能够使用声明式模型在 Redis 中轻松地建模和查询数据,这些模型对于 Peewee、SQLAlchemy 和 Django ORM 的用户来说会很熟悉。

本教程将引导您完成安装 Redis OM for Python、创建第一个模型以及使用它保存和验证数据的过程。

先决条件#

Redis OM 需要 Python 3.9 或更高版本以及要连接的 Redis 实例。

Python#

确保您正在运行 Python 3.9 或更高版本:

python --version
Python 3.9.0

如果您没有安装 Python,可以从 Python.org 下载,使用 Pyenv,或使用操作系统包管理器安装 Python。

Redis#

Redis OM 将数据保存到 Redis 中,因此您需要安装并运行 Redis 才能完成本教程。

下载 Redis#

最新版本的 Redis 可从 Redis.io 获取。您也可以使用操作系统包管理器安装 Redis。

注意

本教程将引导您完成在本地启动 Redis 的过程,但这些说明也适用于在远程服务器上运行 Redis 的情况。

在 Windows 上安装 Redis#

Redis 不能直接在 Windows 上运行,但您可以使用 Windows Subsystem for Linux (WSL) 来运行 Redis。请查看 我们在 YouTube 上的视频 以获取逐步说明。

Windows 用户也可以使用 Docker。有关更多信息,请参阅下一部分关于使用 Docker 运行 Redis 的内容。

使用 Docker 的 Redis#

您可以使用 Docker 运行 Redis,而不是手动安装或使用包管理器安装。

我们推荐 redis-stack 镜像,因为它包含 Redis OM 可以用来提供额外功能的 Redis 模块。本指南的后面部分将详细介绍这些功能。

您也可以使用官方的 Redis Docker 镜像,该镜像托管在 Docker Hub 上。

注意

当我们在本指南中讨论 运行 Redis 时,我们将讨论如何使用 Docker 实际启动 Redis。

Redis OM 依赖于 Redis Stack 的 Search 和 JSON 支持。

您不需要这些 Redis 模块来使用 Redis OM 的数据建模、验证和持久化功能,但我们建议您使用它们以充分利用 Redis OM。

在本地开发期间运行这些 Redis 模块的最简单方法是使用 redis-stack Docker 镜像。

对于其他安装方法,请按照这两个模块主页上的“快速入门”指南进行操作。

启动 Redis#

在开始使用 Redis OM 之前,请确保您已启动 Redis。

启动 Redis 的命令将取决于您安装它的方式。

Ubuntu Linux(包括 WSL)#

如果您使用 apt 安装了 Redis,请使用 systemctl 命令启动它

$ sudo systemctl restart redis.service

否则,您可以手动启动服务器

$ redis-server start

macOS with Homebrew#

$ brew services start redis

Docker#

使用 Docker 启动 Redis 的命令取决于您选择使用的镜像。

提示:这些示例中的 -d 选项在后台运行 Redis,而 -p 6379:6379 使 Redis 可在本地主机上的端口 6379 上访问。

使用 redis-stack 镜像的 Docker(推荐)

$ docker run -d -p 6379:6379 redis/redis-stack

使用 redis 镜像的 Docker

$ docker run -d -p 6379:6379 redis

安装 Redis OM#

安装 Redis OM 的推荐方法是使用 Poetry。您可以使用以下命令使用 Poetry 安装 Redis OM:

$ poetry install redis-om

如果您使用的是 Pipenv,则命令为

$ pipenv install redis-om

最后,您可以使用 pip 通过运行以下命令来安装 Redis OM:

$ pip install redis-om

提示:如果您没有使用 Poetry 或 Pipenv,而是直接使用 pip 安装,我们建议您在虚拟环境(即 virtualenv)中安装 Redis OM。如果您不熟悉此概念,请参阅 Dan Bader 的视频和记录

设置 Redis URL 环境变量#

我们几乎准备好创建 Redis OM 模型了!但首先,我们需要确保 Redis OM 知道如何连接到 Redis。

默认情况下,Redis OM 尝试连接到本地主机上的端口 6379 上的 Redis。大多数本地安装方法都会导致 Redis 在此位置运行,在这种情况下,您无需执行任何特殊操作。

但是,如果您将 Redis 配置为在其他端口上运行,或者如果您使用的是远程 Redis 服务器,则需要设置 REDIS_OM_URL 环境变量。

The REDIS_OM_URL environment variable follows the redis-py URL format

redis://[[username]:[password]]@localhost:6379/[database number]

默认连接等效于以下 REDIS_OM_URL 环境变量:

redis://@localhost:6379

提示: Redis 数据库按编号,默认值为 0。您可以省略数据库编号以使用默认数据库。

其他支持的前缀包括用于 SSL 连接的“rediss”和用于 Unix 域套接字的“unix”。

rediss://[[username]:[password]]@localhost:6379/0
unix://[[username]:[password]]@/path/to/socket.sock?db=0

定义模型#

在本教程中,我们将创建一个 Customer 模型来验证和保存数据。让我们从模型的基本定义开始。我们将在继续的过程中添加功能。

import datetime

from redis_om import HashModel


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

需要注意一些细节

  1. 1.我们的 Customer 模型扩展了 HashModel 类。这意味着它将作为哈希保存到 Redis 中。Redis OM 提供的另一个模型类是 JsonModel,我们将在后面讨论。
  2. 2.我们使用 Python 类型注解指定了模型的字段。

让我们更详细地了解 HashModel 类和类型注解。

HashModel 类#

当您子类化 HashModel 时,您的子类既是 Redis OM 模型,具有将数据保存到 Redis 的方法,又是 Pydantic 模型。

这意味着您可以将 Pydantic 字段验证与 Redis OM 模型一起使用,我们将在稍后讨论验证时进行介绍。但这同时也意味着您可以在任何使用 Pydantic 模型的地方使用 Redis OM 模型,例如在 FastAPI 应用程序中。🤯

类型注解#

您添加到模型字段的类型注解用于以下几个目的

  • 使用 Pydantic 验证器验证数据
  • 将数据序列化到 Redis
  • 从 Redis 反序列化数据

我们将在本教程的整个过程中看到这些示例。

关于 HashModel 类的一个重要细节是,它不支持 listset 或映射(如 dict)类型。这是因为 Redis 哈希不能包含列表、集合或其他哈希。

如果您想对具有列表、集合或映射类型,或其他模型的字段进行建模,您需要使用 JsonModel 类,它可以支持这些类型,以及嵌入式模型。

创建模型#

让我们看看创建模型对象的样子

import datetime

from redis_om import HashModel


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


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="andrew.brookins@example.com",
    join_date=datetime.date.today(),
    age=38,
    bio="Python developer, works at Redis, Inc."
)

可选字段#

如果我们省略了其中一个字段,例如 bio?

import datetime

from redis_om import HashModel
from pydantic import ValidationError


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


# All fields are required because none of the fields
# are marked `Optional`, so we get a validation error:
try:
    Customer(
        first_name="Andrew",
        last_name="Brookins",
        email="andrew.brookins@example.com",
        join_date=datetime.date.today(),
        age=38  # <- We didn't pass in a bio!
    )
except ValidationError as e:
    print(e)
    """
    ValidationError: 1 validation error for Customer
    bio
      field required (type=value_error.missing)
    """

如果我们希望 bio 字段是可选的,我们需要将类型注解更改为使用 Optional.

import datetime
from typing import Optional

from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: str
    join_date: datetime.date
    age: int
    bio: Optional[str]  # <- Now, bio is an Optional[str]

现在我们可以创建 Customer 对象,无论是否有 bio 字段。

默认值#

字段可以具有默认值。您可以通过将值分配给字段来设置它们。

import datetime
from typing import Optional

from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: str
    join_date: datetime.date
    age: int
    bio: Optional[str] = "Super dope"  # <- We added a default here

现在,如果我们创建 Customer 对象,没有 bio 字段,它将使用默认值。

import datetime
from typing import Optional

from redis_om import HashModel


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


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="andrew.brookins@example.com",
    join_date=datetime.date.today(),
    age=38)  # <- Notice, we didn't give a bio!

print(andrew.bio)  # <- So we got the default value.
# > 'Super Dope'

然后,模型将在您下次调用 save() 时将此默认值保存到 Redis。

自动主键#

模型会在不需要与 Redis 交互的情况下自动生成全局唯一的键。

import datetime
from typing import Optional

from redis_om import HashModel


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


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="andrew.brookins@example.com",
    join_date=datetime.date.today(),
    age=38)

print(andrew.pk)
# > '01FJM6PH661HCNNRC884H6K30C'

在您保存模型 之前,ID 即可使用。

默认的 ID 生成函数会创建 ULID,但如果您想使用其他类型的键,您可以更改用于生成模型主键的函数。

验证数据#

Redis OM 使用 Pydantic 来根据您分配给模型类中字段的类型注解验证数据。

此验证确保诸如 first_name 之类的字段(Customer 模型将其标记为 str)始终是字符串。**但是每个 Redis OM 模型也是一个 Pydantic 模型**,因此您可以使用 Pydantic 验证器,如 EmailStrPattern 以及许多其他验证器来进行复杂的验证!

例如,我们之前将 Customer 模型的 join_date 定义为 datetime.date。因此,如果我们尝试创建一个模型,其中 join_date 不是日期,我们将收到验证错误。

现在让我们尝试一下

import datetime
from typing import Optional

from redis_om import HashModel
from pydantic import ValidationError


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


try:
    Customer(
        first_name="Andrew",
        last_name="Brookins",
        email="a@example.com",
        join_date="not a date!",  # <- The problem line!
        age=38
    )
except ValidationError as e:
    print(e)
    """
    pydantic.error_wrappers.ValidationError: 1 validation error for Customer
    join_date
      invalid date format (type=value_error.date)
    """

模型默认会强制转换值#

您可能想知道在我们最后一个验证示例中什么算作“日期”。默认情况下,Redis OM 会尝试将输入值强制转换为正确的类型。这意味着我们可以为 join_date 传递日期字符串,而不是 date 对象

import datetime
from typing import Optional

from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: str
    join_date: datetime.date
    age: int


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="a@example.com",
    join_date="2020-01-02",  # <- We're passing a YYYY-MM-DD date string now
    age=38
)

print(andrew.join_date)
# > 2021-11-02
type(andrew.join_date)
# > datetime.date  # The model parsed the string automatically!

这种将解析(在本例中为 YYYY-MM-DD 日期字符串)与验证相结合的能力可以为您节省大量工作。

但是,您可以关闭强制转换——请查看下一节关于使用严格验证的内容。

严格验证#

您可以打开严格验证,拒绝字段的值,除非它们与模型类型注解的精确类型匹配。

您可以通过将字段的类型注解更改为使用 “Pydantic 提供的严格类型” 中的某个类型来做到这一点。

Redis OM 支持所有 Pydantic 的严格类型:StrictStrStrictBytesStrictIntStrictFloatStrictBool

如果我们想要确保 age 字段只接受整数,并且不尝试解析包含整数的字符串,例如“1”,我们将使用 StrictInt 类。

import datetime
from typing import Optional

from pydantic import StrictInt, ValidationError
from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: str
    join_date: datetime.date
    age: StrictInt  # <- Instead of int, we use StrictInt
    bio: Optional[str]


# Now if we use a string instead of an integer for `age`,
# we get a validation error:
try:
    Customer(
        first_name="Andrew",
        last_name="Brookins",
        email="a@example.com",
        join_date="2020-01-02",  # <- A date as a string shouldn't work now!
        age="38"
    )
except ValidationError as e:
    print(e)
    """
    pydantic.error_wrappers.ValidationError: 1 validation error for Customer
    join_date
      Value must be a datetime.date object (type=value_error)
    """

Pydantic 没有 StrictDate 类,但我们可以创建自己的。在此示例中,我们创建了一个 StrictDate 类型,我们将使用它来验证 join_datedatetime.date 对象。

import datetime
from typing import Optional

from pydantic import ValidationError
from redis_om import HashModel


class StrictDate(datetime.date):
    @classmethod
    def __get_validators__(cls) -> 'CallableGenerator':
        yield cls.validate

    @classmethod
    def validate(cls, value: datetime.date, **kwargs) -> datetime.date:
        if not isinstance(value, datetime.date):
            raise ValueError("Value must be a datetime.date object")
        return value


class Customer(HashModel):
    first_name: str
    last_name: str
    email: str
    join_date: StrictDate
    age: int
    bio: Optional[str]


# Now if we use a string instead of a date object for `join_date`,
# we get a validation error:
try:
    Customer(
        first_name="Andrew",
        last_name="Brookins",
        email="a@example.com",
        join_date="2020-01-02",  # <- A string shouldn't work now!
        age="38"
    )
except ValidationError as e:
    print(e)
    """
    pydantic.error_wrappers.ValidationError: 1 validation error for Customer
    join_date
      Value must be a datetime.date object (type=value_error)
    """

保存模型#

我们可以通过调用 save() 来将模型保存到 Redis:

import datetime

from redis_om import HashModel


class Customer(HashModel):
    first_name: str
    last_name: str
    email: str
    join_date: datetime.date
    age: int


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="andrew.brookins@example.com",
    join_date=datetime.date.today(),
    age=38)

andrew.save()

检查 Redis 中的数据#

您可以查看 Redis 中存储的任何 Redis OM 模型的数据。

首先,获取您要检查的模型实例的键。 key() 方法将为您提供用于存储模型的精确 Redis 键。

注意

此方法的命名可能令人困惑。这不是主键,而是该模型的 Redis 键。因此,方法名称可能会更改。

在此示例中,我们查看了为我们一直在构建的 Customer 模型创建的键:

import datetime
from typing import Optional

from redis_om import HashModel


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


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="andrew.brookins@example.com",
    join_date=datetime.date.today(),
    age=38)

andrew.save()
andrew.key()
# > 'mymodel.Customer:01FKGX1DFEV9Z2XKF59WQ6DC9T'

有了模型的 Redis 键,您可以启动 redis-cli 并检查该键下存储的数据。在此,我们使用 redis-cli 运行 JSON.GET 命令,使用该项目的 Docker Compose 文件定义的正在运行的“redis”容器

$ docker-compose exec -T redis redis-cli HGETALL mymodel.Customer:01FKGX1DFEV9Z2XKF59WQ6DC9r

 1) "pk"
 2) "01FKGX1DFEV9Z2XKF59WQ6DC9T"
 3) "first_name"
 4) "Andrew"
 5) "last_name"
 6) "Brookins"
 7) "email"
 8) "andrew.brookins@example.com"
 9) "join_date"
10) "2021-11-02"
11) "age"
12) "38"
13) "bio"
14) "Super dope"

获取模型#

如果您有模型的主键,您可以调用模型类上的 get() 方法来获取模型的数据。

import datetime
from typing import Optional

from redis_om import HashModel


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


andrew = Customer(
    first_name="Andrew",
    last_name="Brookins",
    email="andrew.brookins@example.com",
    join_date=datetime.date.today(),
    age=38)

andrew.save()

assert Customer.get(andrew.pk) == andrew

使用表达式查询模型#

Redis OM 带有一个丰富的查询语言,允许您使用 Python 表达式查询 Redis。

为了展示它的工作原理,我们将对我们之前定义的 Customer 模型进行一个小改动。我们将添加 Field(index=True) 来告诉 Redis OM 我们想要索引 last_nameage 字段

import datetime
from typing import Optional

from pydantic import EmailStr

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


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
# Redis Stack 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!
Migrator().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()

下一步#

既然您已经了解了 Redis OM 的基本知识,就开始在您的项目中使用它吧!

如果您是 FastAPI 用户,请查看 如何将 Redis OM 与 FastAPI 集成.