在日常开发中,我们使用 Redis 时常常会遇到这样的场景:需要存储复杂的结构化数据(比如用户信息、商品详情),还要支持灵活的查询(按年龄筛选、按技能搜索)。直接用 Redis 的基础命令处理 JSON 数据不仅繁琐,查询起来更是头疼。而 Redis OM for Python 的出现,正好解决了这些问题 —— 它让我们能用 Python 类轻松建模,用简洁的代码实现 CRUD 和复杂查询。今天我们就结合 Flask 框架,手把手教你用 Redis OM Python 构建一个文档型 API。
Redis OM Python 是 Redis 官方推出的客户端工具,专为简化 Redis 中的文档数据管理而生。它基于 Pydantic 框架提供数据建模能力,能自动处理:
简单说,有了它,我们可以像操作 ORM 一样操作 Redis,无需再手写复杂的 Redis 命令。
在开始之前,我们需要准备好开发环境,一步步搭建起基础框架。
首先克隆示例仓库,然后启动 Redis Stack(用 Docker 最方便):
bash
# 克隆仓库
git clone https://github.com/redis-developer/redis-om-python-flask-skeleton-app.git
cd redis-om-python-flask-skeleton-app
# 用Docker启动Redis Stack(包含JSON和搜索功能)
docker-compose up -d
如果用 Redis Cloud,需要配置环境变量(替换为你的 Redis 信息):
bash
export REDIS_OM_URL=redis://default:@:
创建虚拟环境并安装依赖(Flask、Redis OM 等):
bash
# 创建虚拟环境
python3 -m venv venv
# 激活环境(Windows用venv\Scripts\activate)
. ./venv/bin/activate
# 安装依赖
pip install -r requirements.txt
以开发模式启动,修改代码后自动重启:
bash
# 设置开发模式
export FLASK_ENV=development
# 启动应用
flask run
此时访问http://127.0.0.1:5000/
,能看到主页说明启动成功。
运行数据加载脚本,批量添加测试数据到 Redis:
bash
python dataloader.py
输出会显示每个新增用户的 ULID(类似01FX8RMR7NRS45PBT3XP9KNAZH
),后续查询会用到这些 ID。
Redis OM 的核心是数据建模 —— 通过 Python 类定义数据结构,自动映射到 Redis 的 JSON 文档。我们以Person
模型为例,看看如何定义。
在person.py
中,我们定义两个类:Person
(主模型)和Address
(嵌入式模型):
python
from pydantic import Field
from typing import List, Optional
from redis_om import JsonModel, EmbeddedJsonModel, NotFoundError
# 嵌入式地址模型(作为Person的字段)
class Address(EmbeddedJsonModel):
# 街道号码(必选,索引)
street_number: int = Field(index=True)
# 单元号(可选,不索引)
unit: Optional[str] = Field(index=False)
# 街道名称(必选,索引)
street_name: str = Field(index=True)
# 城市(必选,索引)
city: str = Field(index=True)
# 州/省(必选,索引)
state: str = Field(index=True)
# 邮编(必选,索引)
postal_code: str = Field(index=True)
# 国家(必选,索引,默认英国)
country: str = Field(index=True, default="United Kingdom")
# 人员主模型
class Person(JsonModel):
# 名(必选,索引)
first_name: str = Field(index=True)
# 姓(必选,索引)
last_name: str = Field(index=True)
# 年龄(必选,正整数,索引)
age: int = Field(index=True)
# 地址(必选,嵌入式模型)
address: Address
# 个人陈述(必选,支持全文搜索)
personal_statement: str = Field(index=True, full_text_search=True)
# 技能列表(必选,字符串数组,索引)
skills: List[str] = Field(index=True)
Person
继承JsonModel
(表示作为独立 JSON 文档存储),Address
继承EmbeddedJsonModel
(表示嵌入到其他模型中,不单独存储)。index=True
:为字段创建索引,支持查询。full_text_search=True
:开启全文搜索(如personal_statement
)。Optional
:标记可选字段(如unit
),需配合index=False
。default
:设置默认值(如country
默认 "United Kingdom")。pk
属性),格式如01FX8SSSDN7PT9T3N0JZZA758G
。有了模型,我们就能通过简单的代码实现增删改查。下面结合 Flask API 和 curl 命令,逐个演示。
python
# app.py
from flask import Flask, request, jsonify
from person import Person, NotFoundError
app = Flask(__name__)
@app.route("/person/new", methods=["POST"])
def create_person():
# 从请求JSON初始化Person实例
new_person = Person(**request.json)
# 保存到Redis
new_person.save()
# 返回生成的ULID
return new_person.pk
bash
curl --location --request POST 'http://127.0.0.1:5000/person/new' \
--header 'Content-Type: application/json' \
--data-raw '{
"first_name": "Joanne",
"last_name": "Peel",
"age": 36,
"personal_statement": "Music is my life, I love gigging and playing with my band.",
"address": {
"street_number": 56,
"unit": "4A",
"street_name": "The Rushes",
"city": "Birmingham",
"state": "West Midlands",
"postal_code": "B91 6HG",
"country": "United Kingdom"
},
"skills": ["synths", "vocals", "guitar"]
}'
数据在 Redis 中以 JSON 文档存储,键名格式为:person.Person:
。用redis-cli
查看:
bash
redis-cli
127.0.0.1:6379> json.get :person.Person:01FX8SSSDN7PT9T3N0JZZA758G
返回的 JSON 包含所有字段,包括嵌入式address
的详细信息。
Redis OM 的find
方法支持多种查询条件,满足不同场景需求。
python
@app.route("/person/byid/", methods=["GET"])
def find_by_id(id):
try:
# 通过ID获取实例,转换为字典返回
person = Person.get(id)
return person.dict()
except NotFoundError:
return jsonify({"error": "Not found"}), 404
测试:
bash
curl --location --request GET 'http://localhost:5000/person/byid/01FX8SSSDN7PT9T3N0JZZA758G'
python
@app.route("/people/byname//", methods=["GET"])
def find_by_name(first, last):
# 条件:first_name == first 且 last_name == last
people = Person.find(
(Person.first_name == first) &
(Person.last_name == last)
).all()
return jsonify({"results": [p.dict() for p in people]})
测试(查询 Kareem Khan):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/byname/Kareem/Khan'
python
@app.route("/people/byage//", methods=["GET"])
def find_in_age_range(min_age, max_age):
# 条件:age >= min_age 且 age <= max_age,按age排序
people = Person.find(
(Person.age >= int(min_age)) &
(Person.age <= int(max_age))
).sort_by("age").all()
return jsonify({"results": [p.dict() for p in people]})
测试(30-47 岁):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/byage/30/47'
python
@app.route("/people/byskill//", methods=["GET"])
def find_matching_skill(skill, city):
# 条件:skills包含skill 且 address.city == city
# << 表示“包含”(数组查询)
people = Person.find(
(Person.skills << skill) &
(Person.address.city == city)
).all()
return jsonify({"results": [p.dict() for p in people]})
测试(Sheffield 的吉他手):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/byskill/guitar/Sheffield'
python
@app.route("/people/bystatement/", methods=["GET"])
def find_matching_statements(term):
# % 表示全文搜索(匹配包含term的内容)
people = Person.find(Person.personal_statement % term).all()
return jsonify({"results": [p.dict() for p in people]})
测试(搜索含 “play” 的陈述):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/bystatement/play'
注意:全文搜索会匹配近义词(如 “play”“plays”“playing”),非常适合自由文本查询。
python
@app.route("/person//age/", methods=["POST"])
def update_age(id, new_age):
try:
# 获取实例,修改字段后保存
person = Person.get(id)
person.age = int(new_age)
person.save()
return "ok"
except NotFoundError:
return "Not found", 404
测试(更新 Kareem 的年龄为 28):
bash
curl --location --request POST 'http://127.0.0.1:5000/person/01FX8RMR7T60ANQTS4P9NKPKX8/age/28'
python
@app.route("/person//delete", methods=["POST"])
def delete_person(id):
# 直接删除ID对应的记录
Person.delete(id)
return "ok"
测试(删除 ID 为 01FX8RMR8545RWW4DYCE5MSZA1 的记录):
bash
curl --location --request POST 'http://127.0.0.1:5000/person/01FX8RMR8545RWW4DYCE5MSZA1/delete'
通过Person.db()
获取 Redis 连接,调用expire
设置 TTL(生存时间):
python
@app.route("/person//expire/", methods=["POST"])
def expire_by_id(id, seconds):
try:
person = Person.get(id)
# 对实例的键设置过期时间(秒)
Person.db().expire(person.key(), int(seconds))
return "ok"
except NotFoundError:
return "Not found", 404
测试(600 秒后过期):
bash
curl --location --request POST 'http://localhost:5000/person/01FX8RMR82D091TC37B45RCWY3/expire/600'
用redis-cli
验证 TTL:
bash
127.0.0.1:6379> ttl :person.Person:01FX8RMR82D091TC37B45RCWY3
REDIS_OM_URL
是否正确。dataloader.py
依赖的接口正常。index=True
)。Redis OM for Python 让 Redis 的文档型数据管理变得简单:用 Python 类定义模型,用直观的 API 实现 CRUD 和复杂查询,无需手动拼接 Redis 命令。无论是构建小型 API 还是复杂的文档型应用,它都能大幅提升开发效率。
如果本文对你有帮助,别忘了点赞收藏,关注我,一起探索更高效的开发方式~