A drop-in replacement for `redis-om`, built out of frustration.
Project description
Object mapping, and more, for Redis and Python
Redis OM Python makes it easy to model Redis data in your Python applications.
Install the package from PyPI as pyredis-om, then import aredis_om for the async API or redis_om for the generated sync mirror. This release targets Pydantic v2.
📚 The full documentation lives in docs/. This README is just the essentials.
Table of contents
💡 Why Redis OM?
Redis OM provides high-level abstractions that make it easy to model and query data in Redis with modern Python applications.
The current release includes:
- Declarative object mapping for Redis objects
- Declarative secondary-index generation
- Fluent APIs for querying Redis
- Async-first APIs with a generated sync mirror
- Lazy
Meta.databaseresolution, callable connection providers, runtime reassignment - Default model TTLs via
Meta.default_ttl - Bulk fetches with
get_many(), explicit pipeline composition - Redis Cluster (
cluster=Trueor?cluster=truein the URL) - Embedded JSON sorting, GEO queries, vector similarity search (FLAT/HNSW)
- Embedded list containment queries (
Workspace.users << User(name="John")) - Comprehensive token escaping for TAG and TEXT fields
- GEO queries with
Coordinates/GeoFilter, plus rawGEO*access — seedocs/geo_queries.mdx - AtomicCounter backed by Redis 8.8
INCREX— seedocs/atomic_counter.mdx - RedisArray for Redis 8.8+ sparse, index-addressable arrays — see
docs/redis_arrays.mdx - Hash field TTL (
HEXPIRE/HGETEX/HGETDEL/HSETEX) onHashModelfor Redis 7.4+ / 8.0+ — seedocs/hash_field_ttl.mdx - RedisStream wrapper around the
X*family with 8.2/8.4/8.6/8.8 extensions (XACKDEL,XDELEX,XNACK,IDMP,XREADGROUP ... CLAIM) — seedocs/streams.mdx - AtomicString + MSETEX (
SET IFEQ/IFNE,DELEX,DIGEST, bulkMSETEX) for Redis 8.4+ — seedocs/atomic_strings.mdx - Vector sets (
VectorSet:VADD/VSIM/VINFO/VCARD/VEMB/VLINKS/VRANDMEMBER/VREM/VSETATTR/VGETATTR) for Redis 8.8+ — seedocs/vector_sets.mdx - Hot-keys tracker (
HOTKEYS START/GET/STOP/RESET) for Redis 8.6+ — seedocs/hotkeys.mdx - Bitmap operators (
BitmapOps:BITOP DIFF/DIFF1/ANDOR/ONE) for Redis 8.2+ — seedocs/bitmap_ops.mdx - Sorted set aggregations (
SortedSetOps:ZUNION/ZINTERwithAGGREGATE COUNT) for Redis 8.8+ — seedocs/sorted_set_aggregations.mdx - Cluster admin (
ClusterAdmin:CLUSTER SLOT-STATS,CLUSTER MIGRATION ...) for Redis 8.2+ cluster mode — seedocs/cluster_admin.mdx - Keyspace notification helpers (
KeyspaceEvents,build_flags,enable_keyspace_events) for Redis 2.8+ — seedocs/keyspace_notifications.mdx - OpenTelemetry observability wrapper around redis-py 8.0 instrumentation — see
docs/observability.mdx
⚡ Why execute_command?
This fork deliberately does not wrap every redis-py high-level binding (db.ft(...).search(...), db.geoadd(...), etc.). For hot paths like RediSearch, INCREX, and the AR* array commands we call db.execute_command("FT.SEARCH", ...) (or "GEOADD", "INCREX", ...) directly.
| Reason | What it means in practice |
|---|---|
| Faster | No per-call method dispatch or argument coercion; the command name and args go straight to the socket. |
| More predictable | Argument order matches the Redis command reference exactly. db.geoadd(... nx=True, xx=True) raised in some redis-py 5.x versions — execute_command doesn't. |
| Universal | Works the moment Redis ships a command. INCREX (Redis 8.8+), the AR* family (8.8+ preview), and FT.AGGREGATE WITHCURSOR options all worked here before redis-py shipped typed bindings. |
| Cluster-safe | The same call works on redis.Redis and redis.RedisCluster with no API differences. |
The cost is that the caller is responsible for getting the argument order right. See docs/pipelines.mdx for tested examples.
💻 Installation
# pip
pip install pyredis-om
# uv
uv add pyredis-om
🏁 Getting started
Start Redis
docker run -p 6379:6379 redis:8-alpine
export REDIS_OM_URL="redis://localhost:6379?decode_responses=True"
The redis:8-alpine image includes the RedisJSON and RediSearch modules Redis OM needs for JSON and search features. See docs/redis_modules.mdx for other options including Redis Enterprise and OSS-only setups.
Connect
from aredis_om import get_redis_connection
redis_conn = get_redis_connection()
# Or pass an explicit URL:
redis_conn = get_redis_connection(url="redis://localhost:6379?decode_responses=True")
For Redis Cluster, see docs/cluster.mdx. For RESP2/RESP3 protocol negotiation, see docs/protocol.mdx.
Define, save, query
from redis_om import Field, HashModel, Migrator
class Customer(HashModel):
first_name: str
last_name: str = Field(index=True)
age: int = Field(index=True)
Migrator().run()
andrew = Customer(first_name="Andrew", last_name="Brookins", age=38)
andrew.save()
# Reload by primary key
Customer.get(andrew.pk)
# Query — `<<` is the IN operator for TAG fields
Customer.find(Customer.last_name == "Brookins").all()
Customer.find(Customer.age >= 35).sort_by("age").page(offset=0, limit=10)
That's the whole shape. Full reference: docs/models.mdx, docs/queries.mdx.
📇 Modeling your data
Two model classes cover most needs:
from typing import Optional
from redis_om import HashModel, JsonModel, Field, EmbeddedJsonModel
class Customer(HashModel):
first_name: str
last_name: str = Field(index=True)
age: int = Field(index=True)
email: Optional[str] = Field(index=True, default=None)
HashModel— flat, fast, stored as a Redis hash. NoList/Dictfields.JsonModel— for nested structures, embedded models,List[T]/Dict[K, V].EmbeddedJsonModel— a sub-document forJsonModel.addressstyle fields.
Full details, including the lazy Meta.database, Meta.default_ttl, vector fields, and embedded List[EmbeddedJsonModel]: docs/models.mdx.
🔎 Queries, embedded models, and GEO
# Equality, range, AND/OR/NOT
Customer.find(Customer.age >= 35).all()
Customer.find(
(Customer.last_name == "Brookins") | (Customer.first_name == "Kim")
).all()
# IN / NOT IN on TAG fields
Customer.find(Customer.last_name << ["Brookins", "Smith"]).all()
Customer.find(Customer.last_name != "Brookins").all()
# Embedded JsonModel fields
Customer.find(Customer.address.city == "San Antonio").all()
# GEO queries
from redis_om import Coordinates, GeoFilter
class Store(HashModel):
name: str = Field(index=True)
coordinates: Coordinates = Field(index=True)
Store.find(
Store.coordinates == GeoFilter(
longitude=-73.9851, latitude=40.7589, radius=2, unit="mi",
)
).all()
Full syntax — sorting, pagination, cursors, KNN vector search, prefix matches, embedded list containment, GEO + TAG combinations: docs/queries.mdx, docs/geo_queries.mdx.
🧩 Pipelines and raw commands
Compose model queries with raw Redis commands in one round trip:
from aredis_om import HashModel, Field
class Customer(HashModel):
first_name: str
last_name: str = Field(index=True)
# Bulk save + atomic counter increment, in one round trip
pipe = Customer.db().pipeline(transaction=False)
pipe.incr("metrics:signups")
await Customer.add(new_customers, pipeline=pipe)
results = await pipe.execute()
Why execute_command (and not the redis-py typed bindings): see ⚡ Why execute_command? above. Full pipeline patterns — bulk fetches + secondary key lookups, GEO model + raw GEO* storage, KNN + stream publish, rate limiting + writes, cluster hash tags: docs/pipelines.mdx.
📚 Documentation
The full documentation lives in docs/. Highlights:
- Getting started — Overview, Getting Started, Connecting to Redis, Examples
- Models and queries — Models and Fields, Queries and Vector Search, Validation, Error Messages
- Operations — Bulk Operations, Streams, Geospatial Queries, Hash Field Expiration, Pipelines and
execute_command, Migrations - Redis 8.x features — AtomicCounter (
INCREX), Redis Arrays, Atomic Strings (CAS,MSETEX), Vector Sets, Hot Keys Tracker, Bitmap Operations, Sorted Set Aggregations, Cluster Admin, Keyspace Notifications, OpenTelemetry Observability - Deployment — Redis Cluster, Protocol Selection, Redis Modules, FastAPI Integration
- Reference — Upstream Issues Fixed, Pending Features (RedisVL)
❤️ Contributing
See CLAUDE.md for the contributor workflow (async source of truth, make sync regeneration), and SECURITY_REVIEW.md for design notes. Open an issue on GitHub to get started.
Current local coverage baseline: 88% overall across aredis_om/ and the generated redis_om/ mirror, with 1100+ passing async + sync tests. RESP2 vs RESP3 parity is exercised end-to-end by tests/test_protocol_compat.py.
📝 License
Redis OM uses the MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyredis_om-0.8.9.tar.gz.
File metadata
- Download URL: pyredis_om-0.8.9.tar.gz
- Upload date:
- Size: 374.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6feecc1559297c686742046c79a977ec93af3c5892b703e6393dd8a821028f60
|
|
| MD5 |
646681f13e85d80c7aa1ba950481cc4d
|
|
| BLAKE2b-256 |
442d93430e71dc85c6b6db8e1383546358b57e4dedcd9b1968abf5418b8dec99
|
File details
Details for the file pyredis_om-0.8.9-py3-none-any.whl.
File metadata
- Download URL: pyredis_om-0.8.9-py3-none-any.whl
- Upload date:
- Size: 190.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2a783d42cbb7166e6a6dbec8afd451cf3f6b06c052d47b92c46c6210c0d0174
|
|
| MD5 |
73fe0c1bd723b869ae0e192f7471f1d9
|
|
| BLAKE2b-256 |
c4da75d03ccc5202449f6a38474db87506f47bfc2a72297b208e785e764e58aa
|