Redis OM for Python 实战:用 Flask 构建 Redis 文档型 API

在日常开发中,我们使用 Redis 时常常会遇到这样的场景:需要存储复杂的结构化数据(比如用户信息、商品详情),还要支持灵活的查询(按年龄筛选、按技能搜索)。直接用 Redis 的基础命令处理 JSON 数据不仅繁琐,查询起来更是头疼。而 Redis OM for Python 的出现,正好解决了这些问题 —— 它让我们能用 Python 类轻松建模,用简洁的代码实现 CRUD 和复杂查询。今天我们就结合 Flask 框架,手把手教你用 Redis OM Python 构建一个文档型 API。

一、Redis OM Python 是什么?

Redis OM Python 是 Redis 官方推出的客户端工具,专为简化 Redis 中的文档数据管理而生。它基于 Pydantic 框架提供数据建模能力,能自动处理:

  • 唯一 ID 生成(使用 ULID,比 UUID 更友好)
  • Redis 键名的自动生成(无需手动拼接键名)
  • JSON 格式的存储与解析
  • 索引创建与复杂查询(支持条件组合、范围查询、全文搜索等)

简单说,有了它,我们可以像操作 ORM 一样操作 Redis,无需再手写复杂的 Redis 命令。

二、环境准备与快速启动

在开始之前,我们需要准备好开发环境,一步步搭建起基础框架。

2.1 环境要求

  • git:用于克隆示例代码
  • Python 3.9+:运行应用
  • Redis Stack:提供 JSON 和搜索功能(或安装了 JSON、Search 模块的 Redis)
  • curl/Postman:测试 API
  • (可选)Redis Insight:可视化查看 Redis 数据

2.2 步骤 1:获取源码并启动 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:@:

2.3 步骤 2:配置 Python 环境

创建虚拟环境并安装依赖(Flask、Redis OM 等):

bash

# 创建虚拟环境
python3 -m venv venv
# 激活环境(Windows用venv\Scripts\activate)
. ./venv/bin/activate
# 安装依赖
pip install -r requirements.txt

2.4 步骤 3:启动 Flask 应用

以开发模式启动,修改代码后自动重启:

bash

# 设置开发模式
export FLASK_ENV=development
# 启动应用
flask run

此时访问http://127.0.0.1:5000/,能看到主页说明启动成功。

2.5 步骤 4:加载示例数据

运行数据加载脚本,批量添加测试数据到 Redis:

bash

python dataloader.py

输出会显示每个新增用户的 ULID(类似01FX8RMR7NRS45PBT3XP9KNAZH),后续查询会用到这些 ID。

三、核心:用 Redis OM 构建数据模型

Redis OM 的核心是数据建模 —— 通过 Python 类定义数据结构,自动映射到 Redis 的 JSON 文档。我们以Person模型为例,看看如何定义。

3.1 模型定义:Person 与嵌入式 Address

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")。
  • 自动 ID:Redis OM 自动生成 ULID 作为主键(pk属性),格式如01FX8SSSDN7PT9T3N0JZZA758G

四、CRUD 操作实战:从 API 到 Redis 存储

有了模型,我们就能通过简单的代码实现增删改查。下面结合 Flask API 和 curl 命令,逐个演示。

4.1 创建(Create):新增人员数据

代码实现(Flask 路由):

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
测试(curl 命令):

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的详细信息。

4.2 读取(Read):灵活查询数据

Redis OM 的find方法支持多种查询条件,满足不同场景需求。

4.2.1 按 ID 查询

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'
4.2.2 按姓名查询(多条件组合)

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'
4.2.3 按年龄范围查询(带排序)

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'
4.2.4 按城市和技能查询(数组包含)

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'
4.2.5 全文搜索个人陈述

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”),非常适合自由文本查询。

4.3 更新(Update):修改已有数据

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'

4.4 删除(Delete):移除数据

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'

4.5 高级:设置数据过期时间

通过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

五、常见问题排查

  • Flask 启动失败:检查 Redis 是否启动(Docker 容器是否运行),Redis Cloud 的REDIS_OM_URL是否正确。
  • 数据加载失败:确保 Flask 应用已启动,dataloader.py依赖的接口正常。
  • 查询无结果:检查条件是否正确(如姓名区分大小写),索引是否设置(index=True)。

总结

Redis OM for Python 让 Redis 的文档型数据管理变得简单:用 Python 类定义模型,用直观的 API 实现 CRUD 和复杂查询,无需手动拼接 Redis 命令。无论是构建小型 API 还是复杂的文档型应用,它都能大幅提升开发效率。

如果本文对你有帮助,别忘了点赞收藏,关注我,一起探索更高效的开发方式~

你可能感兴趣的:(Redis OM for Python 实战:用 Flask 构建 Redis 文档型 API)