Redis OM for Python 使您能够使用声明式模型在 Redis 中轻松地建模和查询数据,这些模型对于 Peewee、SQLAlchemy 和 Django ORM 的用户来说会很熟悉。
本教程将引导您完成安装 Redis OM for Python、创建第一个模型以及使用它保存和验证数据的过程。
Redis OM 需要 Python 3.9 或更高版本以及要连接的 Redis 实例。
确保您正在运行 Python 3.9 或更高版本:
python --version
Python 3.9.0
如果您没有安装 Python,可以从 Python.org 下载,使用 Pyenv,或使用操作系统包管理器安装 Python。
Redis OM 将数据保存到 Redis 中,因此您需要安装并运行 Redis 才能完成本教程。
最新版本的 Redis 可从 Redis.io 获取。您也可以使用操作系统包管理器安装 Redis。
本教程将引导您完成在本地启动 Redis 的过程,但这些说明也适用于在远程服务器上运行 Redis 的情况。
Redis 不能直接在 Windows 上运行,但您可以使用 Windows Subsystem for Linux (WSL) 来运行 Redis。请查看 我们在 YouTube 上的视频 以获取逐步说明。
Windows 用户也可以使用 Docker。有关更多信息,请参阅下一部分关于使用 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 OM 之前,请确保您已启动 Redis。
启动 Redis 的命令将取决于您安装它的方式。
如果您使用 apt
安装了 Redis,请使用 systemctl
命令启动它
$ sudo systemctl restart redis.service
否则,您可以手动启动服务器
$ redis-server start
$ brew services start redis
使用 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 的推荐方法是使用 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 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
需要注意一些细节
Customer
模型扩展了 HashModel
类。这意味着它将作为哈希保存到 Redis 中。Redis OM 提供的另一个模型类是 JsonModel
,我们将在后面讨论。让我们更详细地了解 HashModel
类和类型注解。
当您子类化 HashModel
时,您的子类既是 Redis OM 模型,具有将数据保存到 Redis 的方法,又是 Pydantic 模型。
这意味着您可以将 Pydantic 字段验证与 Redis OM 模型一起使用,我们将在稍后讨论验证时进行介绍。但这同时也意味着您可以在任何使用 Pydantic 模型的地方使用 Redis OM 模型,例如在 FastAPI 应用程序中。🤯
您添加到模型字段的类型注解用于以下几个目的
我们将在本教程的整个过程中看到这些示例。
关于 HashModel
类的一个重要细节是,它不支持 list
、set
或映射(如 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 验证器,如 EmailStr
、Pattern
以及许多其他验证器来进行复杂的验证!
例如,我们之前将 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 的严格类型:StrictStr
、StrictBytes
、StrictInt
、StrictFloat
和 StrictBool
。
如果我们想要确保 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_date
是 datetime.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 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_name
和 age
字段
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 集成.